파이썬 3.15 새 기능 정리 - lazy import·frozendict·UTF-8 기본값 (베타 4 기준) 🐍
목차17개 항목
안녕하세요! Mojangcam입니다 😎
파이썬 3.15가 드디어 베타 4(3.15.0b4) 까지 나왔어요. 2026년 7월 18일에 공개된 이번 베타가 마지막 베타라서, 지금 들어간 기능들이 사실상 정식 버전 그대로 나온다고 보시면 됩니다.
이번 글에서는 파이썬 3.15에서 가장 크게 바뀌는 세 가지 — lazy import, frozendict, 그리고 UTF-8 기본 인코딩 을 초보자도 이해할 수 있게 정리해볼게요. 특히 UTF-8 기본값은 윈도우에서 한글 파일 읽다가 UnicodeDecodeError 로 고생해보신 분이라면 진짜 반가운 소식이에요! 🎉

📌 먼저 출시 일정부터 확인해요 🗓️
- 베타 4 (마지막 베타): 2026-07-18 ✅
- RC 1: 2026-08-04
- RC 2: 2026-09-01
- 정식 출시: 2026-10-01
새 기능 추가가 멈추는 기능 동결(feature freeze)은 이미 베타 1(2026-05-07) 때 지나갔어요. 즉 지금은 버그만 잡는 단계라, 미리 공부해두기 딱 좋은 타이밍이죠!
📌 준비물
- 파이썬 3.15 베타 — python.org 다운로드 페이지의 Pre-releases 항목에서 받을 수 있어요
- 기존 파이썬 3.13 / 3.14 — 지우지 말고 그대로 두세요
- 가상환경(venv) — 베타는 반드시 격리해서 테스트!
베타를 기본 파이썬으로 덮어쓰면 기존 프로젝트가 다 터질 수 있어요. 꼭 별도 설치 + 가상환경으로 돌려보세요.
# 설치 확인
python3.15 --version
# Python 3.15.0b4
# (윈도우라면) py -3.15 --version
# 가상환경 만들고 활성화
python3.15 -m venv venv315
source venv315/bin/activate # 윈도우: venv315\Scripts\activate가상환경이 아직 낯설다면 이 글부터 보고 오세요 👉 파이썬 가상환경(venv) 완벽 정리
📌 1단계 - lazy import, 프로그램 시작이 확 빨라져요 🚀
제일 화제인 기능이에요. PEP 810에서 들어온 명시적 지연 임포트(explicit lazy imports) 입니다.
🧠 왜 필요한가요?
파이썬은 import pandas 를 만나는 순간 무조건 바로 그 모듈을 통째로 읽어서 실행해요. 문제는, 그 무거운 모듈을 실제로는 특정 명령을 실행할 때만 쓰는 경우가 많다는 거죠.
CLI 도구나 디스코드 봇처럼 "켜지는 속도"가 중요한 프로그램에서 이게 꽤 아파요. --help 한 번 보려고 3초를 기다리는 상황이 생기니까요.
🧩 어떻게 쓰나요?
lazy 라는 소프트 키워드를 import 앞에 붙이기만 하면 돼요.
lazy import json
lazy from pathlib import Path
print("시작!") # 여기까지 json, pathlib 안 불러옴
data = json.loads('{"a": 1}') # ← 바로 이 순간 json이 로딩돼요
p = Path(".") # ← 이 순간 pathlib이 로딩돼요하나씩 뜯어볼게요 💡
- lazy : 새로 생긴 소프트 키워드예요. 기존에
lazy를 변수명으로 쓰던 코드는 그대로 동작하니 걱정 안 하셔도 돼요. - 프록시(proxy) 객체 :
lazy import는 진짜 모듈 대신 가벼운 대역 객체를 이름에 묶어둬요. 이름을 실제로 사용하는 순간 진짜 모듈로 바뀝니다. - 첫 사용 시점 : 로딩뿐 아니라 에러도 그때 발생해요. 대신 트레이스백에 "어디서 썼는지"와 "어디서 import 했는지"가 둘 다 찍혀서 추적하기 쉬워요.
⚠️ 아무 데나 못 써요
lazy 는 모듈 최상단(전역)에서만 쓸 수 있어요. 아래는 전부 SyntaxError 입니다.
def run():
lazy import json # ❌ 함수 안 → SyntaxError
class Bot:
lazy import json # ❌ 클래스 안 → SyntaxError
try:
lazy import json # ❌ try/except/finally 안 → SyntaxError
except ImportError:
pass
lazy from os import * # ❌ 별표 임포트 → SyntaxError
lazy from __future__ import annotations # ❌ future 임포트 → SyntaxError🛠️ 구버전 호환이 필요하다면 __lazy_modules__
파이썬 3.14 이하도 같이 지원해야 하면 lazy 키워드를 못 써요(문법 에러가 나니까요). 이럴 땐 모듈 전역에 __lazy_modules__ 를 선언하면 됩니다.
__lazy_modules__ = ["json", "pathlib"]
import json # 3.15에서는 자동으로 lazy
import os # 목록에 없으니 그대로 즉시 로딩3.14 이하에서는 그냥 평범한 리스트 변수라 무시되고, 3.15부터만 지연 임포트로 동작해요. 똑똑하죠? 💡
다만 목록에 적은 모듈은 "지연될 수 있는(potentially lazy)" 상태가 되는 거지, 무조건 지연된다는 보장은 아니에요. 아래에서 설명할 전역 모드나 필터 설정에 따라 그냥 즉시 로딩될 수도 있고, 해당 모듈의 서브모듈 import까지 자동으로 따라오진 않아요.
💡 보너스 - 전체를 한 번에 lazy로
내 코드를 안 고치고 통째로 테스트해보고 싶다면 실행 옵션으로 켤 수 있어요.
python3.15 -X lazy_imports=all app.py
# 또는
PYTHON_LAZY_IMPORTS=all python3.15 app.py기본값은 normal 이고, 이건 "소스에 적힌 lazy 키워드만 존중한다"는 뜻이에요. 런타임에서 확인·변경하려면 sys.get_lazy_imports() / sys.set_lazy_imports() 를 쓰면 됩니다. 더 세밀하게 고르고 싶다면 sys.set_lazy_imports_filter() 로 "어떤 모듈만 lazy로" 같은 조건도 줄 수 있어요.
📌 2단계 - frozendict, 못 바꾸는 딕셔너리 🧊
list 에 tuple 이 있고 set 에 frozenset 이 있듯이, 이제 dict 에도 짝꿍이 생겼어요. PEP 814의 frozendict 입니다. 별도 설치 없이 builtins 에 들어가 있어서 그냥 쓰면 돼요.
config = frozendict(host="127.0.0.1", port=8080)
print(config["host"]) # 127.0.0.1 (읽기는 dict와 똑같아요)
config["port"] = 9999 # TypeError!하나씩 뜯어볼게요 💡
- 불변(immutable) : 만들고 나면 못 바꿔요.
dict에 있던__setitem__,__delitem__,clear(),pop(),popitem(),setdefault(),update()이 7개가 빠졌어요. 나머지 읽기 API는dict와 거의 같아요. - dict의 자식이 아니에요 :
frozendict는dict를 상속하지 않고object를 바로 상속해요. 그래서isinstance(x, dict)로 검사하던 코드는isinstance(x, (dict, frozendict))또는isinstance(x, collections.abc.Mapping)으로 바꿔줘야 해요. ⚠️ - 해시 가능 : 키와 값이 전부 해시 가능하면
frozendict자체도 해시돼요. 즉 딕셔너리의 키나 set의 원소로 쓸 수 있어요! 값에 리스트가 들어가면 해시는 안 됩니다. - 순서 : 넣은 순서는 유지되지만, 비교할 때는 순서를 안 따져요. 해시값도 순서와 무관하고요.
🧩 만드는 방법 (대표 세 가지)
frozendict(host="localhost", port=8080) # 키워드 인자
frozendict({"host": "localhost", "port": 8080}) # 기존 dict에서
frozendict([("host", "localhost"), ("port", 8080)]) # (키, 값) 튜플 목록🧠 어디에 쓰면 좋을까요?
실수로 바뀌면 안 되는 설정값, 함수의 기본 인자(가변 기본값 버그 방지), 그리고 캐시 키로 쓰기 딱 좋아요. 지금까지는 설정을 캐시 키로 쓰려면 tuple(sorted(d.items())) 같은 걸 만들어야 했는데, 이제 그냥 frozendict 를 키로 넣으면 끝이에요.
표준 라이브러리 중 copy, decimal, json, marshal, pickle, pprint, xml.etree.ElementTree 가 frozendict 를 받아들이도록 업데이트됐고, plistlib 은 직렬화에 한해 지원해요.
📌 3단계 - UTF-8이 드디어 기본값! 🇰🇷
한국 개발자에게는 이게 오늘의 진짜 주인공일지도 몰라요. PEP 686이 적용되면서 파이썬이 시스템 로케일과 상관없이 UTF-8을 기본 인코딩으로 사용합니다.
무슨 뜻이냐면요, 지금까지 윈도우 한국어 환경에서는 이랬어요.
# 파이썬 3.14 이하 · 윈도우 한국어 환경
open("메모.txt").read()
# → cp949로 읽으려다 UnicodeDecodeError 💥같은 코드가 맥에서는 잘 되는데 윈도우에서만 터지던, 그 유명한 문제죠. 3.15부터는 encoding 을 안 적어도 UTF-8로 읽고 씁니다. 팀원끼리 OS가 달라도 결과가 같아져요! 🙌
# 파이썬 3.15
open("메모.txt").read() # UTF-8로 읽어요 ✅하나씩 뜯어볼게요 💡
- 적용 범위 :
encoding인자를 안 넘겼을 때만 바뀌어요. 직접encoding="cp949"라고 적은 코드는 그대로 동작해요. - 로케일 인코딩이 필요하면 :
encoding="locale"을 쓰면 됩니다. 이 옵션은 3.10부터 있었어요. - 되돌리려면 :
PYTHONUTF8=0환경변수나-X utf8=0옵션으로 예전 동작으로 돌아갈 수 있어요. - 가장 안전한 습관 : 버전에 상관없이 항상
encoding을 명시하는 거예요. 이건 3.15가 나와도 변하지 않는 정답입니다.
크롤링한 결과를 파일로 저장할 때 인코딩 때문에 고생하셨다면 이 글도 같이 보시면 좋아요 👉 파이썬 업무 자동화 #2 - 웹 크롤링 입문
📌 전체 코드 (복붙 OK)
세 가지를 한 번에 확인해보는 스크립트예요. 파이썬 3.15에서 그대로 돌려보세요!
# Python 3.15.0b4 기준 (정식 출시 2026-10-01 예정)
# 파일명: py315_demo.py
# 실행: python3.15 py315_demo.py
# 1) lazy import - 최상단(전역)에서만 쓸 수 있어요
lazy import json
lazy from pathlib import Path
import sys
import locale
def check_lazy():
print("[1] lazy import")
# 아직 json이라는 이름은 '쓴' 적이 없으니 로딩 전이에요
print(" json 로드됨? ->", "json" in sys.modules)
data = json.loads('{"blog": "Mojangcam"}') # ← 여기서 진짜 로딩!
print(" json 로드됨? ->", "json" in sys.modules)
print(" 파싱 결과 ->", data)
print(" 현재 모드 ->", sys.get_lazy_imports())
def check_frozendict():
print("\n[2] frozendict")
config = frozendict(host="127.0.0.1", port=8080)
print(" 값 읽기 ->", config["host"], config["port"])
try:
config["port"] = 9999
except TypeError as e:
print(" 수정 시도 ->", e)
# 순서가 달라도 같은 값이면 동일! 그래서 캐시 키로 딱이에요
same = frozendict(port=8080, host="127.0.0.1")
print(" 순서 무시 비교 ->", config == same)
cache = {config: "설정 A"}
print(" dict 키로 사용 ->", cache[same])
def check_utf8():
print("\n[3] UTF-8 기본 인코딩")
# locale.getencoding()은 UTF-8 모드와 무관하게 OS 로케일을 그대로 알려줘요
print(" 로케일 인코딩 ->", locale.getencoding())
p = Path("hangul_test.txt") # ← 여기서 pathlib 로딩!
p.write_text("안녕하세요 Mojangcam입니다!") # encoding 안 적었어요
print(" 읽은 내용 ->", p.read_text())
p.unlink()
if __name__ == "__main__":
check_lazy()
check_frozendict()
check_utf8()📌 실행 결과
[1] lazy import
json 로드됨? -> False
json 로드됨? -> True
파싱 결과 -> {'blog': 'Mojangcam'}
현재 모드 -> normal
[2] frozendict
값 읽기 -> 127.0.0.1 8080
수정 시도 -> 'frozendict' object does not support item assignment
순서 무시 비교 -> True
dict 키로 사용 -> 설정 A
[3] UTF-8 기본 인코딩
로케일 인코딩 -> cp949
읽은 내용 -> 안녕하세요 Mojangcam입니다!
실제 실행 화면
첫 줄의 json 로드됨? -> False 가 핵심이에요. import는 분명 파일 맨 위에 적었는데 실제 로딩은 json.loads() 를 호출한 다음에야 일어났죠. 이게 lazy import입니다! 🎉
그리고 [3] 을 보면 로케일은 여전히 cp949 인데도 한글과 이모지가 멀쩡히 저장되고 읽히는 걸 볼 수 있어요. (로케일 인코딩 값은 환경마다 다르게 나올 수 있어요)
📌 이것도 같이 바뀌어요 💡
- sentinel 내장 타입 (PEP 661) —
None을 기본값으로 못 쓸 때 만들던_MISSING = object()관용구가 정식 기능이 됐어요. - 컴프리헨션에서 언패킹 (PEP 798) —
[*L for L in lists]처럼 중첩 리스트를 한 줄로 펼칠 수 있어요. - 새 프로파일링 패키지 (PEP 799) —
profiling.tracing(cProfile이 여기로 옮겨왔어요)과profiling.sampling(신규 샘플링 프로파일러 Tachyon)로 정리됐어요.cProfile은 호환용 별칭으로 그대로 남지만, 오래된profile모듈은 deprecated 되어 3.17에서 제거 예정이에요. - 에러 메시지 개선 —
[1,2,3].push(4)처럼 다른 언어 습관대로 쓰면 ".append를 쓰려던 거 아닌가요?" 하고 알려줘요. 입문자에게 진짜 좋은 변화예요 🙌 - GC 롤백 유지 — 3.14.0~3.14.4에 들어갔던 증분 GC가 메모리 이슈로 3.13의 세대별 GC로 되돌려졌고(3.14.5부터 적용), 3.15도 그대로 세대별 GC를 쓰게 돼요.
- 모듈 3개 삭제 —
sre_compile,sre_constants,sre_parse가 완전히 제거됐어요.
📌 자주 나는 에러
1. SyntaxError — lazy import not allowed inside functions
- 원인:
lazy키워드를 함수·클래스·try 블록 안에서 썼어요. - 해결: 모듈 최상단으로 옮기세요. 함수 안에서 늦게 불러오고 싶다면 그냥 기존처럼 함수 안에 일반
import를 쓰면 됩니다(원래 그게 지연 로딩이니까요).
2. ModuleNotFoundError 가 엉뚱한 곳에서 터져요
- 원인: lazy import는 첫 사용 시점에 로딩되니, 오타나 미설치 패키지 오류도 그때 나타나요.
- 해결: 트레이스백에 import 위치가 같이 찍히니 그걸 보고 찾으세요. 그래도 헷갈리면 해당 줄을 잠시 일반
import로 되돌려 확인하면 금방 잡혀요.
3. TypeError: 'frozendict' object does not support item assignment
- 원인:
frozendict는 만든 뒤에 못 바꿔요. - 해결: 수정 대신 새로 만드세요. 예)
new = frozendict({**old_dict, "port": 9999})
4. 3.14까지 잘 읽히던 파일이 3.15에서 깨져요
- 원인: 그 파일이 cp949(확장 완성형)로 저장된 옛날 파일인데, 3.15는 기본을 UTF-8로 읽거든요. 방향만 반대일 뿐 익숙한 그 문제예요.
- 해결:
open(path, encoding="cp949")처럼 명시하거나, 로케일을 따르려면encoding="locale"을 쓰세요. 프로젝트 전체를 예전처럼 돌리려면PYTHONUTF8=0또는-X utf8=0입니다.
5. isinstance(x, dict) 가 frozendict 에서 False
- 원인:
frozendict는dict의 서브클래스가 아니에요. - 해결:
isinstance(x, (dict, frozendict))또는isinstance(x, collections.abc.Mapping)으로 바꾸세요.
📌 마무리
오늘은 파이썬 3.15의 핵심 세 가지를 정리해봤어요! 💬
- lazy import — 첫 사용 시점까지 로딩을 미뤄서 시작 속도를 크게 줄여줘요. 모듈 최상단에서만 쓸 수 있어요.
- frozendict — 못 바꾸는 딕셔너리. 해시가 되니 캐시 키·설정값으로 딱이에요.
- UTF-8 기본값 — 윈도우 한글 인코딩 지웥에서 드디어 탈출! 대신 옛날 cp949 파일은
encoding을 명시해주세요.
정식 출시는 2026년 10월 1일이니까 아직 시간이 있어요. 지금은 가상환경에 베타 하나 깔아두고 내 코드가 잘 도는지 미리 돌려보는 정도가 딱 좋습니다. 특히 인코딩 명시 안 한 open() 이 있는지 한 번 훑어보시는 걸 추천해요!
참고로 이 글은 3.15.0b4 기준이라, 공식 문서도 아직 draft 상태예요. 정식 출시 전까지 세부 내용이 조금 달라질 수 있으니 공식 What's New 문서도 한 번씩 확인해보세요 🙂
다음 편은 파이썬 업무 자동화 #3 - 파일 정리 자동화(이름 일괄 변경) 로 찾아올게요. 다운로드 폴더에 뒤죽박죽 쌓인 파일들을 파이썬으로 싹 정리하는 방법이에요. 기대해주세요! 🙌
궁금한 점이나 3.15에서 겪은 문제가 있다면 댓글로 남겨주세요 💬
