본문으로 건너뛰기

© 2026 Molayo

GeekNews헤드라인2026. 05. 23. 03:40

Python 3.15: 헤드라인을 장식하지 못한 기능들

요약

Python 3.15.0b1 기능 동결에 따라 확정된 실용적인 개선 사항들을 소개합니다. asyncio의 TaskGroup 취소 방식 간소화, 컨텍스트 매니저 개선, threading 및 JSON 파싱 기능 업데이트 등 주요 변화를 다룹니다.

핵심 포인트

  • TaskGroup.cancel() 도입으로 예외 처리 없이 우아한 태스크 취소 가능
  • ContextDecorator가 비동기 함수 및 제너레이터 생애주기를 지원하도록 개선
  • threading 유틸리티를 통한 스레드 간 반복자 직렬화/복제 지원
  • Counter xor 연산 추가 및 json.loads의 불변 JSON 파싱 지원

Python 3.15.0b1 기능 동결로 지연 임포트와 Tachyon 프로파일러 외에도 실용적인 개선들이 확정됨
asyncio

TaskGroup.cancel() 은 사용자 정의 예외와 contextlib.suppress

없이 태스크 그룹을 우아하게 취소함
ContextDecorator는 비동기 함수·제너레이터·비동기 반복자의 전체 생애주기를 감싸도록 바뀜
threading 새 유틸리티는 반복자 소비를 스레드 간 직렬화하거나 복제해 Queue 없이 추상화를 유지하게 해줌
Counter

에는 xor 연산이 추가되고, json.loads

array_hook

frozendict

로 불변 JSON 파싱을 지원함

Python 3.15의 덜 알려진 변화

  • Python 3.15.0b1 기능 동결로 올해 Python에 들어갈 기능이 확정됐으며, 큰 변화로는 지연 임포트와 Tachyon 프로파일러가 있음
  • Python 3.15에는 큰 PEP만큼 눈에 띄지는 않지만 실용적인
    작은 기능 변화도 포함되며, asyncio

, 컨텍스트 매니저, 스레드 안전 반복자, Counter

, JSON 파싱 쪽 개선이 들어감

asyncio TaskGroup 취소

asyncio

의 핵심 변화로 TaskGroup을 우아하게 취소할 수 있는 기능이 추가됨
TaskGroup

은 구조적 동시성의 한 형태로, 여러 동시 작업을 깔끔하게 생성하고 모두 완료될 때까지 기다릴 수 있게 함

async with asyncio.TaskGroup() as tg:
tg.create_task(run())
tg.create_task(run())
# Waits for all the tasks to complete
  • Python 3.15 이전에는 백그라운드 신호를 기다렸다가
    TaskGroup

실행을 중단하려면 사용자 정의 예외를 발생시키고 contextlib.suppress

로 걸러내야 했음

class Interrupt(Exception):
...
with suppress(Interrupt):
async with asyncio.TaskGroup() as tg:
tg.create_task(run())
tg.create_task(run())
if await wait_for_signal():
raise Interrupt()
  • 이 방식은 태스크 그룹 안에서 예외가 발생하면 다른 태스크가 취소되고, 사용자 정의
    Interrupt

예외가 ExceptionGroup

의 일부로 발생한 뒤 contextlib.suppress에 의해 필터링되기 때문에 동작함
ExceptionGroup

과 함께 동작하는 suppress

의 방식은 Python 3.12에서 추가됐지만 크게 주목받지 못했음

  • Python 3.15의 TaskGroup.cancel은 같은 작업을 훨씬 단순하게 만듦
async with asyncio.TaskGroup() as tg:
tg.create_task(run())
tg.create_task(run())
if await wait_for_signal():
tg.cancel()

TaskGroup.cancel()

은 예외를 발생시키지 않고 그룹을 취소하므로, 별도 예외와 suppress

조합이 필요 없어짐

컨텍스트 매니저 개선

  • 컨텍스트 매니저는 Python 3.3부터
    데코레이터로도 직접 사용할 수 있었음
@contextmanager
def duration(message: str) -> Iterator[None]:
start = time.perf_counter()
try:
yield
finally:
print(f"{message} elapsed {time.perf_counter() - start:.2f} seconds")
@duration('workload')
def workload():
...
# Or simple as a wrapper
duration('stuff')(other_workload)(...)
  • 블록 실행 시간을 출력하는
    duration()

같은 컨텍스트 매니저는 함수 데코레이터처럼 쓸 수 있어 편리하지만, 비동기 함수, 제너레이터, 비동기 반복자에서는 제대로 동작하지 않는 경우가 있었음

@duration('async workload')
async def async_workload():
...
@duration('generator workload')
def workload():
while True:
yield ...
  • 반복자, 비동기 함수, 비동기 반복자는 일반 함수와 의미론이 달라 호출 즉시 각각 제너레이터 객체, 코루틴 객체, 비동기 제너레이터 객체를 반환함
  • 기존 데코레이터는 감싸는 대상의 전체 생애주기를 포괄하지 못하고 즉시 완료되어, 실제 실행 시간 전체를 감싸지 못했음
  • Python 3.15에서는
    ContextDecorator

가 감싸는 함수의 타입을 확인하고, 데코레이터가 해당 대상의 전체 생애주기를 덮도록 바뀜

  • 컨텍스트 매니저를 데코레이터로 쓸 때 생기던 흔한 함정을 피하고 더 깔끔한 문법을 사용할 수 있음

스레드 안전 반복자

  • 반복자는 Python의 핵심 추상화 중 하나로, 데이터 소스와 데이터 소비자를 분리해 더 깔끔한 구조를 만들 수 있음
lazy from typing import Iterator
def stream_events(...) -> Iterator[str]:
while True:
yield blocking_get_event(...)
events = stream_events(...)
for event in events:
consume(event)
  • 이 추상화는 스레딩이나 자유 스레딩 환경에서 깨질 수 있으며, 기본 반복자는
    스레드 안전하지 않아 값이 건너뛰어지거나 내부 반복자 상태가 망가질 수 있음
  • Python 3.15의 threading.serialize_iterator는 기존 반복자를 감싸 스레드 간 소비를 직렬화함
import threading
events = threading.serialize_iterator(stream_events(...))
with ThreadPoolExecutor() as executor:
fut1 = executor.submit(consume, events)
fut2 = executor.submit(consume, events)
source1, source2 = threading.concurrent_tee(squares(10), n=2)
with ThreadPoolExecutor() as executor:
fut1 = executor.submit(consume, source1)
fut2 = executor.submit(consume, source2)
  • 기존에는 스레드 간 소비를 동기화하기 위해 주로 Queue에 의존했지만, 새 유틸리티를 쓰면 멀티스레드 코드에서도 기존 반복자 추상화를 바꾸지 않고 유지할 수 있음

추가 기능

c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
print(f"{c + d = }") # add two counters together: c[x] + d[x]
print(f"{c - d = }") # subtract (keeping only positive counts)
Counter(a=4, b=3)
Counter(a=1, b=0)

Counter

에는 교집합과 합집합에 해당하는 &

, |

연산도 있음

print(f"{c & d = }") # intersection: min(c[x], d[x])
print(f"{c | d = }") # union: max(c[x], d[x])
Counter(a=1, b=1)
Counter(a=3, b=2)

Counter

는 이산 객체의 집합처럼 볼 수 있으며, 예시는 다음과 같은 식으로 해석할 수 있음

{a_0, a_1, a_2, b_0} & {a_0, b_0, b_1} == {a_0, b_0}
{a_0, a_1, a_2, b_0} | {a_0, b_0, b_1} == {a_0, a_1, a_2, b_0, b_1}
  • Python 3.15에서는 여기에
    xor 연산이 추가됨
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
c ^ d == c | d - c & d == Counter(a=3, b=2) - Counter(a=1, b=1) == Counter(a=2, b=1)
{a_0, a_1, a_2, b_0} ^ {a_0, b_0, b_1} == {a_1, a_2, b_1}

Counter

의 집합 연산을 자주 쓰지 않았다면 xor의 구체적 사용처를 떠올리기 어렵지만, 연산 완성도 측면에서 추가된 기능임

불변 JSON 객체

  • Python 3.15에 frozendict가 추가되면서 JSON 타입인 배열, 불리언, 실수, null, 문자열, 객체를 모두
    불변이고 해시 가능한 형태로 표현할 수 있게 됨
  • json.load와 json.loads에
    array_hook

매개변수가 추가되어 기존 object_hook

을 보완함
array_hook=tuple

, object_hook=frozendict

를 함께 쓰면 JSON 객체를 바로 불변 구조로 파싱할 수 있음

json.loads('{"a": [1, 2, 3, 4]}', array_hook=tuple, object_hook=frozendict) == frozendict({'a': (1, 2, 3, 4)})

AI 자동 생성 콘텐츠

본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0