Python 3.15 출시 — 달라진 점 총정리, 한국어 Windows 사용자가 꼭 확인할 UTF-8 기본값 변경
Python 3.15가 2026년 10월 9일 정식 출시됐습니다. 이번 버전에는 import를 필요할 때까지 미루는 lazy import, 바꿀 수 없는 딕셔너리 frozendict, 새 프로파일러 등 눈에 띄는 기능이 많습니다.
그중에서도 한국어 Windows 사용자라면 꼭 알아야 할 변경이 있습니다. 파일을 열 때 기본 인코딩이 UTF-8로 바뀐 것입니다. 지금까지 잘 돌던 코드가 3.15에서 갑자기 UnicodeDecodeError를 낼 수 있습니다.
이 글에서는 3.15의 주요 변경 사항을 예제와 함께 정리하고, 업그레이드 전에 확인할 것을 체크리스트로 담았습니다.
📌 내용과 예제는 Python 3.15 공식 변경 사항 문서(What's New)와 각 PEP 문서를 바탕으로 정리했습니다.
한눈에 보기
| 변경 |
한 줄 요약 |
영향 |
| UTF-8 기본 인코딩 (PEP 686) |
encoding 없이 파일을 열면 OS 설정과 상관없이 UTF-8 |
⚠️ 한국어 Windows에서 기존 코드가 깨질 수 있음 |
| lazy import (PEP 810) |
lazy import json처럼 쓰면 실제로 쓸 때 불러옴 |
프로그램 시작 속도 향상 |
컴프리헨션 안의 *, ``** (PEP 798) |
[*L for L in lists]로 리스트 펼치기 |
코드가 짧아짐 |
frozendict (PEP 814) |
바꿀 수 없는 딕셔너리가 기본 내장 |
딕셔너리를 키나 캐시로 쓰기 쉬워짐 |
| 더 친절한 오류 메시지 |
list.push → ".append 아니에요?" |
초보자에게 특히 도움 |
| 새 프로파일러 Tachyon (PEP 799) |
실행 중인 프로그램에 붙어서 느린 곳을 찾음 |
성능 분석이 쉬워짐 |
| JIT 컴파일러 개선 |
벤치마크에서 평균 7~12% 빨라짐 (JIT 빌드 기준) |
아직 실험적 기능 |
지원 기간: 3.15는 2031년 10월까지 버그·보안 업데이트를 받습니다.
1. ⚠️ 파일 기본 인코딩이 UTF-8로 (한국어 Windows 필독)
무엇이 바뀌었나
파이썬에서 open()으로 파일을 열 때 encoding을 지정하지 않으면, 지금까지는 운영체제의 지역 설정(locale)을 따랐습니다.
| 환경 |
3.14까지 기본 인코딩 |
3.15부터 기본 인코딩 |
| macOS, 대부분의 Linux |
UTF-8 |
UTF-8 (변화 없음) |
| 한국어 Windows |
cp949 |
UTF-8 |
macOS와 Linux는 원래 UTF-8을 쓰고 있었기 때문에, 공식 문서도 "이 변경은 주로 Windows 사용자에게 영향을 준다"고 설명합니다.
어떤 문제가 생길 수 있나
엑셀에서 "CSV(쉼표로 분리)"로 저장한 파일은 한국어 Windows에서 보통 cp949로 저장됩니다. 3.14까지는 아래 코드가 문제없이 동작했습니다.
with open("매출.csv") as f: # encoding을 지정하지 않음
print(f.read())
3.15에서는 같은 코드가 이 파일을 UTF-8로 읽으려고 해서 오류가 납니다.
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb8 in position 0: invalid start byte
반대로 3.15에서 encoding 없이 저장한 파일은 UTF-8이 되기 때문에, cp949를 기대하는 오래된 프로그램에서 열면 한글이 깨져 보일 수 있습니다.
해결 방법
① 가장 좋은 방법: encoding을 항상 명시하기
# 파일이 UTF-8이면
with open("data.txt", encoding="utf-8") as f:
...
# 엑셀 등에서 만든 cp949 파일이면
with open("매출.csv", encoding="cp949") as f:
...
# 운영체제 지역 설정을 따르고 싶다면 (예전 동작)
with open("매출.csv", encoding="locale") as f:
...
encoding을 직접 적어두면 파이썬 버전이나 운영체제가 바뀌어도 똑같이 동작합니다. 3.15와 상관없이 원래 권장되는 습관입니다.
② 기존 코드에서 고칠 곳 찾기
encoding 없이 파일을 여는 곳에서 경고를 띄워주는 옵션이 있습니다.
python -X warn_default_encoding my_script.py
EncodingWarning이 나오는 줄마다 encoding="utf-8" 또는 encoding="locale"을 추가하면 됩니다.
③ 급할 때: 예전 동작으로 되돌리기
당장 코드를 고칠 수 없다면, UTF-8 기본값을 끄고 실행할 수 있습니다.
# 명령줄 옵션
python -X utf8=0 my_script.py
# 또는 환경 변수 (PowerShell)
$env:PYTHONUTF8="0"
임시 방편이므로, 시간이 날 때 ①번 방법으로 고치는 것을 추천합니다.
💡 엑셀 CSV를 UTF-8로 저장하는 방법: 엑셀의 "다른 이름으로 저장"에서 파일 형식을 "CSV UTF-8(쉼표로 분리)"로 고르면 처음부터 UTF-8로 저장됩니다.
💡 지금 운영체제의 원래 인코딩이 궁금하다면 import locale; print(locale.getencoding())로 확인할 수 있습니다. 한국어 Windows에서는 보통 cp949가 나옵니다.
2. lazy import: 필요할 때 불러오기
왜 필요한가
파이썬 프로그램은 시작할 때 파일 맨 위의 import를 모두 실행합니다. 그래서 무거운 라이브러리를 많이 import하는 명령줄 도구는 --help만 입력해도 몇 초씩 걸리기도 합니다. 공식 제안서(PEP 810)는 실제 사례에서 시작 시간이 5070% 줄고, 메모리 사용이 3040% 줄었다고 소개합니다.
사용법
lazy import json
lazy from pathlib import Path
print("시작합니다...") # 아직 json, pathlib을 불러오지 않음
data = json.loads('{"key": "value"}') # 여기서 json을 실제로 불러옴
p = Path(".") # 여기서 pathlib을 실제로 불러옴
lazy를 붙이면 이름만 먼저 등록해 두고, 그 이름을 처음 사용하는 순간 모듈을 불러옵니다.
규칙과 주의할 점
| 규칙 |
내용 |
| 쓸 수 있는 곳 |
파일 최상단(모듈 수준)에서만. 함수, 클래스, try 블록 안에서는 문법 오류 |
| 쓸 수 없는 형태 |
lazy from module import *, lazy from __future__ import ... |
| import 오류 시점 |
모듈이 없을 때 import 줄이 아니라 처음 사용하는 줄에서 오류가 남 |
⚠️ 오류가 나는 위치가 달라진다는 점을 기억하세요. 오타가 있는 모듈 이름을 lazy import하면, 프로그램이 한참 실행된 뒤 그 모듈을 쓰는 순간에야 오류가 납니다.
예전 버전과 함께 쓰려면
lazy 키워드는 3.14 이하에서 문법 오류가 납니다. 여러 버전을 지원해야 하는 코드라면, 모듈에 __lazy_modules__ 목록을 적는 방법이 있습니다. 3.15 이상에서는 이 목록의 모듈이 lazy로 동작하고, 이전 버전에서는 무시되고 평소처럼 import됩니다.
__lazy_modules__ = ["json", "pathlib"]
import json
from pathlib import Path
3. 컴프리헨션에서 *, **로 펼치기
리스트 안의 리스트를 하나로 펼칠 때, 지금까지는 for를 두 번 써야 했습니다. 3.15부터는 *로 바로 펼칠 수 있습니다.
lists = [[1, 2], [3, 4], [5]]
# 3.14까지
[x for L in lists for x in L] # [1, 2, 3, 4, 5]
# 3.15부터
[*L for L in lists] # [1, 2, 3, 4, 5]
딕셔너리 여러 개를 합칠 때는 **를 씁니다. 같은 키가 있으면 뒤에 오는 값이 남습니다.
dicts = [{"a": 1}, {"b": 2}, {"a": 3}]
{**d for d in dicts} # {'a': 3, 'b': 2}
리스트, 세트, 딕셔너리 컴프리헨션과 제너레이터 표현식 모두에서 쓸 수 있습니다.
4. frozendict: 바꿀 수 없는 딕셔너리
frozendict가 list, dict처럼 기본 내장 타입으로 추가됐습니다. 한 번 만들면 내용을 바꿀 수 없습니다.
config = frozendict(host="localhost", port=8080)
config["port"] = 9090
# TypeError: 'frozendict' object does not support item assignment
어디에 쓰나요?
| 용도 |
이유 |
| 딕셔너리를 딕셔너리의 키나 세트의 원소로 |
일반 dict는 해시할 수 없어서 키로 못 쓰지만, frozendict는 가능 |
| 설정값, 상수 묶음 |
실수로 값을 바꾸는 버그를 막음 |
캐시(functools.lru_cache)를 쓰는 함수의 인자 |
해시 가능한 인자만 캐시할 수 있기 때문 |
a = frozendict(x=1, y=2)
b = frozendict(y=2, x=1)
a == b # True (순서가 달라도 내용이 같으면 같음)
hash(a) == hash(b) # True
참고로 frozendict는 dict의 하위 클래스가 아닙니다. isinstance(a, dict)는 False입니다.
5. 더 친절해진 오류 메시지
다른 언어에서 쓰던 메서드 이름을 쓰면, 파이썬에서 맞는 이름을 알려줍니다.
>>> [1, 2].push(3)
AttributeError: 'list' object has no attribute 'push'. Did you mean '.append'?
>>> {}.put("k", "v")
AttributeError: 'dict' object has no attribute 'put'. Use d[k] = v.
자바스크립트(push)나 자바(put)를 쓰다가 파이썬으로 넘어온 분들이 자주 하는 실수를 바로잡아 줍니다.
6. 새 프로파일러 Tachyon: 느린 곳 찾기
"프로그램이 왜 느리지?"를 찾는 프로파일러가 새로 정리됐습니다. 새 profiling 패키지 안에 두 가지가 있습니다.
| 모듈 |
방식 |
특징 |
profiling.sampling (Tachyon) |
샘플링: 짧은 간격으로 지금 실행 중인 위치를 기록 |
프로그램 속도에 거의 영향을 주지 않음. 실행 중인 프로세스에 붙을 수 있음 |
profiling.tracing |
추적: 모든 함수 호출을 기록 |
정확하지만 느려짐 (기존 cProfile과 같은 방식) |
사용 예
# 스크립트를 실행하며 분석
python -m profiling.sampling run script.py
# 결과를 플레임 그래프(HTML)로 저장
python -m profiling.sampling run --flamegraph -o profile.html script.py
# 이미 실행 중인 프로세스(PID 12345)에 30초 동안 붙어서 분석
python -m profiling.sampling attach --flamegraph -d 30 -o profile.html 12345
플레임 그래프는 어떤 함수가 시간을 많이 쓰는지 가로 길이로 보여주는 그림입니다. 막대가 넓은 함수부터 살펴보면 됩니다.
실행 중인 프로세스에 붙는 기능이 특히 유용합니다. 서버나 자동화 프로그램처럼 계속 돌고 있는 프로그램을 멈추지 않고 어디서 시간을 쓰는지 볼 수 있습니다. 라즈베리파이에서 24시간 돌리는 프로그램을 점검할 때도 쓸 수 있습니다 (라즈베리파이에서 파이썬 백그라운드 실행하기 참고).
알아둘 점
- 실행 중인 프로세스에 붙으려면 관리자 권한이 필요합니다 (Linux는 root 또는 ptrace 권한, Windows는 관리자 권한).
- 프로파일러와 대상 프로그램은 같은 파이썬 버전(둘 다 3.15)이어야 합니다.
--live(실시간 화면) 모드는 Windows에서는 지원되지 않습니다.
- 기존
profile 모듈은 사용 중단 예정(3.17에서 제거)이고, cProfile은 계속 쓸 수 있습니다.
7. 성능: JIT 컴파일러 개선
실험적 기능인 JIT 컴파일러가 크게 개선됐습니다. 공식 문서에 따르면 JIT가 포함된 빌드에서:
- x86-64 Linux: 표준 인터프리터보다 벤치마크 평균 7~8% 빠름
- Apple Silicon(AArch64) macOS: 평균 11~12% 빠름
다만 작업 종류에 따라 느려지는 경우(최대 약 15%)도 있고 2배 이상 빨라지는 경우도 있어서, 내 프로그램에서 직접 측정해 보는 것이 정확합니다. 아직 실험적 기능이라는 점도 기억해 두세요.
이 밖에도 base64 인코딩은 약 2배, 디코딩은 약 3배 빨라지는 등 표준 라이브러리 곳곳의 속도가 개선됐습니다.
8. 사라지거나 바뀌는 것
| 항목 |
변경 |
profile 모듈 |
사용 중단 예정 → 3.17에서 제거. cProfile 또는 profiling.tracing 사용 |
re.match() |
re.prefixmatch()로 이름 변경 권장 (기존 match()는 삭제하지 않음) |
표준 라이브러리 모듈의 __version__ 등 |
사용 중단 예정 → 3.20에서 제거. sys.version_info 사용 |
python -m http.server --cgi |
제거됨 |
sre_compile, sre_constants, sre_parse 모듈 |
제거됨 |
strptime()에서 연도 없이 %d만 쓰는 경우 |
이제 ValueError 발생 |
지금 업그레이드해야 할까?
새 프로젝트나 개인 스크립트라면 3.15로 시작해도 좋습니다. 다만 운영 중인 프로그램은 아래를 먼저 확인하세요.
업그레이드 전 체크리스트
# Windows: 3.15를 설치한 뒤 3.15로 가상환경 만들기
py -3.15 -m venv .venv315
.venv315\Scripts\Activate.ps1
pip install -r requirements.txt
python my_script.py
가상환경을 처음 쓰신다면 파이썬 가상환경(venv) 설정 완벽 정리 글을 참고하세요. 3.15는 python.org 다운로드 페이지에서 받을 수 있습니다.
정리
| 하고 싶은 일 |
3.15에서는 |
| 파일 열기 |
항상 encoding="utf-8" (또는 "cp949") 명시. 안 쓰면 이제 UTF-8 |
| 고칠 곳 찾기 |
python -X warn_default_encoding script.py |
| 프로그램 시작 속도 개선 |
lazy import 모듈 |
| 리스트 펼치기 |
[*L for L in lists] |
| 바꿀 수 없는 딕셔너리 |
frozendict(...) |
| 느린 곳 찾기 |
python -m profiling.sampling run --flamegraph -o profile.html script.py |
| 업그레이드 테스트 |
py -3.15 -m venv .venv315로 따로 테스트 |
새 버전이 나오면 바로 업그레이드하기보다, 가상환경에서 먼저 돌려보는 습관이 가장 안전합니다. 특히 한국어 Windows에서 엑셀 CSV를 다루는 코드가 있다면, 이번 기회에 모든 open()에 encoding을 적어두세요.