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

> Clean Markdown view of GeekNews topic #29767. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=29767](https://news.hada.io/topic?id=29767)
- GeekNews Markdown: [https://news.hada.io/topic/29767.md](https://news.hada.io/topic/29767.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-05-23T01:37:05+09:00
- Updated: 2026-05-23T01:37:05+09:00
- Original source: [blog.changs.co.uk](https://blog.changs.co.uk/python-315-features-that-didnt-make-the-headlines.html)
- Points: 1
- Comments: 1

## Topic Body

- **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](https://docs.python.org/3.15/whatsnew/3.15.html) 기능 동결로 올해 Python에 들어갈 기능이 확정됐으며, 큰 변화로는 [지연 임포트](https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-lazy-imports)와 [Tachyon 프로파일러](https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-sampling-profiler)가 있음
- Python 3.15에는 큰 PEP만큼 눈에 띄지는 않지만 실용적인 **작은 기능 변화**도 포함되며, `asyncio`, 컨텍스트 매니저, 스레드 안전 반복자, `Counter`, JSON 파싱 쪽 개선이 들어감

### asyncio TaskGroup 취소
- `asyncio`의 핵심 변화로 [TaskGroup](https://docs.python.org/3/library/asyncio-task.html#asyncio.TaskGroup)을 **우아하게 취소**할 수 있는 기능이 추가됨
- `TaskGroup`은 [구조적 동시성](https://en.wikipedia.org/wiki/Structured_concurrency)의 한 형태로, 여러 동시 작업을 깔끔하게 생성하고 모두 완료될 때까지 기다릴 수 있게 함

```python
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`로 걸러내야 했음

```python
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](https://docs.python.org/3/library/contextlib.html#contextlib.suppress)에 의해 필터링되기 때문에 동작함
- `ExceptionGroup`과 함께 동작하는 `suppress`의 방식은 Python 3.12에서 추가됐지만 크게 주목받지 못했음
- Python 3.15의 [TaskGroup.cancel](https://docs.python.org/3.15/library/asyncio-task.html#asyncio.TaskGroup.cancel)은 같은 작업을 훨씬 단순하게 만듦

```python
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부터 **데코레이터**로도 직접 사용할 수 있었음

```python
@contextmanager
def duration(message: str) -> Iterator[None]:
    start = time.perf_counter()
    try:
        yield
    finally:
        print(f"{message} elapsed {time.perf_counter() - start:.2f} seconds")
```

```python
@duration('workload')
def workload():
    ...





# Or simple as a wrapper
duration('stuff')(other_workload)(...)
```

- 블록 실행 시간을 출력하는 `duration()` 같은 컨텍스트 매니저는 함수 데코레이터처럼 쓸 수 있어 편리하지만, **비동기 함수**, 제너레이터, 비동기 반복자에서는 제대로 동작하지 않는 경우가 있었음

```python
@duration('async workload')
async def async_workload():
    ...

@duration('generator workload')
def workload():
    while True:
        yield ...
```

- 반복자, 비동기 함수, 비동기 반복자는 일반 함수와 의미론이 달라 호출 즉시 각각 제너레이터 객체, 코루틴 객체, 비동기 제너레이터 객체를 반환함
- 기존 데코레이터는 감싸는 대상의 전체 생애주기를 포괄하지 못하고 즉시 완료되어, 실제 실행 시간 전체를 감싸지 못했음
- Python 3.15에서는 `ContextDecorator`가 감싸는 함수의 타입을 확인하고, 데코레이터가 해당 대상의 **전체 생애주기**를 덮도록 바뀜
- 컨텍스트 매니저를 데코레이터로 쓸 때 생기던 흔한 함정을 피하고 더 깔끔한 문법을 사용할 수 있음

### 스레드 안전 반복자
- 반복자는 Python의 핵심 추상화 중 하나로, 데이터 소스와 데이터 소비자를 분리해 더 깔끔한 구조를 만들 수 있음

```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](https://docs.python.org/3.15/library/threading.html#threading.serialize_iterator)는 기존 반복자를 감싸 스레드 간 소비를 직렬화함

```python
import threading

events = threading.serialize_iterator(stream_events(...))

with ThreadPoolExecutor() as executor:
    fut1 = executor.submit(consume, events)
    fut2 = executor.submit(consume, events)
```

- [threading.synchronized_iterator](https://docs.python.org/3.15/library/threading.html#threading.synchronized_iterator)는 제너레이터 함수의 결과에 `threading.serialize_iterator`를 적용하는 데코레이터임
- [threading.concurrent_tee](https://docs.python.org/3.15/library/threading.html#threading.concurrent_tee)는 값을 분할하지 않고 여러 반복자에 **복제**함

```python
source1, source2 = threading.concurrent_tee(squares(10), n=2)

with ThreadPoolExecutor() as executor:
    fut1 = executor.submit(consume, source1)
    fut2 = executor.submit(consume, source2)
```

- 기존에는 스레드 간 소비를 동기화하기 위해 주로 [Queue](https://docs.python.org/3/library/queue.html)에 의존했지만, 새 유틸리티를 쓰면 멀티스레드 코드에서도 기존 반복자 추상화를 바꾸지 않고 유지할 수 있음

### 추가 기능
- ## Counter xor 연산
  - [collections.Counter](https://docs.python.org/3/library/collections.html#collections.Counter)는 이산적 발생 빈도를 쉽게 셀 수 있는 클래스이며, `dict[KeyType, int]`와 비슷하게 동작하면서 여러 유용한 연산을 제공함

```python
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)
```

```python
Counter(a=4, b=3)
Counter(a=1, b=0)
```

  - `Counter`에는 교집합과 합집합에 해당하는 `&`, `|` 연산도 있음

```python
print(f"{c & d = }") # intersection: min(c[x], d[x])
print(f"{c | d = }") # union: max(c[x], d[x])
```

```python
Counter(a=1, b=1)
Counter(a=3, b=2)
```

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

```python
{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 연산**이 추가됨

```python
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)
```

```python
{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](https://peps.python.org/pep-0814/)가 추가되면서 JSON 타입인 배열, 불리언, 실수, null, 문자열, 객체를 모두 **불변이고 해시 가능한 형태**로 표현할 수 있게 됨
  - [json.load](https://docs.python.org/3.15/library/json.html#json.load)와 [json.loads](https://docs.python.org/3.15/library/json.html#json.loads)에 `array_hook` 매개변수가 추가되어 기존 `object_hook`을 보완함
  - `array_hook=tuple`, `object_hook=frozendict`를 함께 쓰면 JSON 객체를 바로 불변 구조로 파싱할 수 있음

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

## Comments



### Comment 58086

- Author: neo
- Created: 2026-05-23T01:37:06+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=48220696) 
- 예시를 보면 `lazy from typing import Iterator`처럼 쓰는데, Python에 드디어 **지연 import**가 생긴 건가 싶음  
  이 변경을 놓친 것 같은데, 이것도 **Python 3.15**부터인지 아니면 이전 버전에도 있던 건지 궁금함
  - 3.15 기능임: [https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-...](<https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-lazy-imports>)
  - 여기서 **지연 import**가 주는 이점이 뭔지 모르겠음. 어차피 모듈 범위의 타입 힌트에서 그 값을 쓰면 import가 필요하지 않나?  
    그러려면 **어노테이션 지연 평가**가 필요할 텐데, 기본으로 켜져 있지는 않은 걸로 알고 있음
  - 이전 Python 버전에서도 모듈 수준에 `def __getattr__(name: str) -> object:`를 구현하면 우회 가능함
  - 이건 Python 3.15의 대표 기능 중 하나라서 이 글에는 빠진 듯함. What's New 문서에서도 첫 번째로 언급되니 확실히 **대표 기능**으로 봐도 됨  
    개인적으로 정말 기대 중임. 바로 이번 주에도 애플리케이션에 실제로 쓰지 않는 모듈 import가 추가된 것만으로 Python 프로세스가 메모리 한계를 넘어서 **메모리 부족**이 난 걸 봤음
  - Python은 거의 첫날부터 함수 안에 `import` 문을 넣는 식의 **지연 import**가 가능했음. 그 함수가 호출되기 전까지는 라이브러리가 import되지 않음

- `frozendict`가 3.15에 추가되면서 이제 JSON의 모든 타입, 즉 배열, 불리언, 부동소수점, null, 문자열, 객체를 **불변이자 해시 가능한 형태**로 표현할 수 있게 됨  
  이 마지막 기능은 정말 마음에 듦

- Python 3.15에 **Iterator 동기화 원시 도구**가 추가된 게 좋음: [https://docs.python.org/3.15/library/threading.html#iterator...](<https://docs.python.org/3.15/library/threading.html#iterator-synchronization>)  
  내가 만든 `threaded-generator` 패키지도 스레드/프로세스 + 생성기 + 큐로 바로 이걸 하고 있는데, 잘 보완해 줄 듯함: [https://pypi.org/project/threaded-generator/](<https://pypi.org/project/threaded-generator/>)

- `Counter`의 집합 연산에서 특히 xor의 사용처를 떠올리기 어렵다고 했는데, **대칭 차집합**을 보면 됨  
  [https://en.wikipedia.org/wiki/Symmetric_difference](<https://en.wikipedia.org/wiki/Symmetric_difference>)
  - 맞긴 한데 `Counter`에 적용하면 **다중집합의 대칭 차집합**이 되고, 이건 자연스러운 정의가 없음  
    제안을 제대로 이해했다면 각 원소 개수 차이의 절댓값으로 정의하는 것 같은데, 이건 결합법칙도 성립하지 않음. 패리티만 본다면 `F_2`에서의 덧셈으로 해석할 수 있어 더 자연스럽지만, 그래도 실제로 어디에 쓸지는 잘 안 보임

- `Counter` 예시 중 하나는 틀렸음. 3.13과 3.15.0a 둘 다에서 확인했음  
  `Counter(a=3, b=1) - Counter(a=1, b=2)`의 결과는 `Counter({'a': 2})`임
  - 나도 그걸 봤음. 문서에 따르면 `Counter` 객체를 결합해 **다중집합**을 만들기 위한 여러 수학 연산이 제공되고, 덧셈과 뺄셈은 대응하는 원소 개수를 더하거나 빼며, 교집합과 합집합은 각각 최소/최대 개수를 반환함  
    각 연산은 음수 개수를 가진 입력도 받을 수 있지만, 출력에서는 개수가 0 이하인 결과를 제외함. 어쨌든 멋진 Counter-example임 ;-)

- 10년 동안 Python에 정말 빠져 있었고 작업하기 즐거웠지만, **AI 코드봇 이후의 세계**에서는 올해만 이미 10만 줄 넘게 지우고 더 빠른 언어로 옮겼음. 요즘은 주로 Go로 옮기는 중임
  - 처음에는 간단하겠지만, 앞으로 그 프로젝트들의 **유지보수**, 특히 더 복잡한 기능 추가는 어떻게 할 생각인지 궁금함  
    한 가지 방법은 Python으로 프로토타입을 만들고 변환하는 식일 수 있겠음
  - Go는 과학 계산이나 머신러닝 작업에는 정말 별로임. 라이브러리가 갖춰져 있지 않고, C API를 감싸는 이야기도 LLM 도움을 받아도 약함  
    필터, 윈도잉, 오버랩 같은 게 들어간 **신호 처리** 코드를 써보면, 현재 라이브러리만으로는 쉽게 할 방법이 거의 없음
  - Go용으로 Django 같은 종합적인 **웹 프레임워크**를 계속 찾고 있음. 그런 게 나오면 바로 꽂힐 듯함
  - 애초에 왜 Python을 쓰게 됐는지 궁금함. 프로그래밍을 전혀 모르는 사람에게는 무엇을 추천하겠음?
  - 흥미롭네. 괜찮다면 그게 **업무 프로젝트**였는지 개인 프로젝트였는지 궁금함

- Python 내부 구조와 운영, 특히 **free-threading**과 관련해 좋은 인터뷰가 있음: [https://alexalejandre.com/programming/interview-with-ngoldba...](<https://alexalejandre.com/programming/interview-with-ngoldbaum/>)

- 아, 내 사랑 Python. 거의 15년 동안 너를 썼음. 그립지만 이제는 더 이상 쓰지 않음. 네 잘못은 아니고, 삶이 바뀌었음
  - 요즘의 **현대적인 Python**은 회사 일과 개인 프로젝트 양쪽에서 정말 즐겁게 쓰고 있음
  - Python과 잘 연동되면서도 짐은 덜 가진, 더 강력한 **Python 비슷한 언어**를 누가 만들고 있나?

- 반복자, 비동기 함수, 비동기 반복자는 일반 함수와 의미가 달라서 데코레이터와 잘 맞지 않았음. 호출하면 각각 생성기 객체, 코루틴 함수, 비동기 생성기 객체를 즉시 반환하므로, 데코레이터가 감싸는 전체 생명주기가 아니라 즉시 끝나버림  
  3.15에서는 `ContextDecorator`가 감싸는 함수 타입을 확인해 데코레이터가 전체 생명주기를 덮도록 바뀌는데, 아이디어는 아주 마음에 들지만 **선택 적용 장치 없이** 기존 사용처의 동작을 미묘하게 바꾸는 점은 꽤 위험해 보임. 누군가 예전의 깨진 방식으로 의도적으로 데코레이터를 썼어야 문제가 되는 “스페이스바 난방” 같은 상황이긴 해도, 실제로 그랬다면 예상치 못하게 깨질 수 있음
  - Python 코어 팀은 기존 동작에 의존하는 사람이 있을 가능성을 낮게 보는 듯함: [https://github.com/python/cpython/pull/136212#issuecomment-4...](<https://github.com/python/cpython/pull/136212#issuecomment-4332309019>)
  - 최악의 경우가 뭐겠음? 호환되지 않는 변경 때문에 개발자들이 예전 Python 버전을 계속 쓰는 정도? 그런 일이 생길 리 없잖아

- 이런 작은 기능들이 결국 가장 유용해지는 경우가 많음. 특히 현재 프로젝트에서 새 **표준 라이브러리 추가 기능**들을 시험해 보고 싶음
