PEP 713 – 호출 가능 객체인 모듈
- Author:
- Amethyst Reese <amethyst at n7.gg>
- Sponsor:
- Łukasz Langa <lukasz at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 20-Apr-2023
- Python-Version:
- 3.12
- Post-History:
- 23-Apr-2023
- Resolution:
- Discourse message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
거부 공지
운영 위원회(Steering Council)는 일관성 관점에서 분명히 구현할 수 있는 일이기는 하지만, 이 PEP를 채택할 만한 설득력 있는 이유가 있다고 보지 않았습니다. 앞으로 이 아이디어가 다시 제기된다면, 이 논의는 참고할 만한 유용한 선행 논의가 될 것입니다.
초록
현재 모듈은 직접 호출할 수 있는 호출 가능 객체가 아닙니다. 클래스는 인스턴스 객체를 호출 가능 객체로 만드는 __call__ 메서드를 정의할 수 있지만, 전역 모듈 스코프에 같은 이름의 함수를 정의해도 아무 효과가 없으며, 그 함수는 임포트하거나 module.__call__로 직접 참조해야만 호출할 수 있습니다. PEP 562는 모듈에 대한 __getattr__() 및 __dir__() 지원을 추가했지만, __call__에 대한 값을 반환하도록 __getattr__를 정의해도 여전히 모듈을 호출할 수 없습니다.
이 PEP는 모듈의 전역 네임스페이스에 __call__ 객체를 일반 함수나 임의의 호출 가능 객체로 정의하여 모듈을 직접 호출할 수 있게 하는 기능을 제안합니다.
동기
많은 모듈은 기능을 제공하는 주요 인터페이스를 하나만 가지고 있습니다. 많은 경우 그 인터페이스는 하나의 호출 가능 객체이며, 이때 모듈을 임포트하여 바로 호출 가능 객체로 사용할 수 있다면 사용자에게 더 “파이썬다운” 인터페이스를 제공할 수 있습니다:
# user.py
import fancy
@fancy
def func(...):
...
현재 이런 스타일의 인터페이스를 제공하려면 런타임에 모듈 객체를 수정하여 호출 가능 객체로 만들어야 합니다.
이는 보통 sys.modules에 있는 모듈 객체를 호출 가능 객체인 대체물(함수나 클래스 인스턴스 등)로 교체하는 방식으로 이루어집니다:
# fancy.py
def fancy(...):
...
sys.modules[__name__] = fancy
이렇게 하면 작성자가 별도의 훅을 추가하지 않는 한, from module import member를 사용하더라도 원래 모듈에 사실상 접근할 수 없게 됩니다. 또한 __doc__, __package__, __path__ 등 모듈의 특수 속성이 모두 빠진 “모듈” 객체가 만들어집니다.
또는 모듈 작성자가 모듈의 __class__ 속성을 호출 가능 객체로서의 인터페이스를 제공하는 사용자 정의 타입으로 재정의할 수도 있습니다:
# fancy.py
def fancy(...):
...
class FancyModule(types.ModuleType):
def __call__(self, ...):
return fancy(...)
sys.modules[__name__].__class__ = FancyModule
두 방식 모두 보일러플레이트가 늘어난다는 단점이 있습니다. 게다가 타입 검사기는 런타임에 모듈이 호출 가능 객체라는 것을 인식하지 못하므로 타입 검사기 오류도 발생합니다:
$ mypy user.py
user.py:3: error: Module not callable [operator]
Found 1 error in 1 file (checked 1 source file)
명세
모듈 객체가 호출될 때 (__getattr__ 또는 __dict__ 조회의 결과로) __call__ 객체가 발견되면, 그 객체가 주어진 인자로 호출됩니다.
__call__ 객체가 발견되지 않으면, 기존 동작과 마찬가지로 TypeError가 발생합니다.
이 예제들은 모두 유효한 호출 가능 객체 모듈로 간주됩니다:
# hello.py
def __call__(...):
pass
# hello.py
class Hello:
pass
__call__ = Hello
# hello.py
def hello():
pass
def __getattr__(name):
if name == "__call__":
return hello
타입 검사기 같은 도구가 정적 분석을 더 쉽게 할 수 있으므로 일반적으로 처음 두 방식이 권장되지만, 구현의 일관성을 높이기 위해 세 번째 형태도 허용됩니다.
이 제안의 의도는 임의의 호출 가능 객체를 모듈의 __call__ 속성에 할당하거나 모듈의 __getattr__ 메서드가 반환할 수 있도록 허용하여, 모듈 작성자가 사용자에게 모듈을 호출 가능하게 제공하는 가장 적합한 방식을 선택할 수 있게 하는 것입니다.
하위 호환성과 성능에 미치는 영향
이 PEP는 하위 호환성 문제를 일으키지 않을 것으로 예상됩니다. 이미 __call__ 객체를 포함하고 있는 모듈은 이전과 동일하게 동작하며, 다만 함수처럼 직접 호출될 수 있는 기능이 추가됩니다. 기존에 __call__ 객체를 가진 모듈이 호출 시 TypeError를 발생시키는 기존 동작에 의존할 가능성은 낮다고 판단됩니다.
이 PEP는 새로운 인터페이스를 정의하는 것이므로 성능에 미치는 영향은 미미합니다. 모듈을 호출하면 모듈 객체에서 __call__이라는 이름에 대한 조회가 일어납니다. 호출 가능 객체 모듈을 만들기 위한 기존 우회 방법들은 이미 일반 객체에서 이 동작에 의존하고 있으므로, 이러한 호출 가능 객체 모듈도 비슷한 성능을 보입니다.
타입 검사기는 __call__ 객체가 있는 모듈을 호출 가능 객체로 취급하도록 그에 맞게 갱신되어야 할 것입니다. 호출 가능 객체 모듈을 지원하지 않는 이전 Python 버전을 대상으로 하는 코드를 검사할 때도 타입 검사기는 이를 지원할 수 있을 것입니다. 이러한 모듈에는 모듈을 호출 가능 객체로 만들기 위해 앞서 언급한 우회 방법 중 하나도 들어 있을 것으로 예상되기 때문입니다.
교육 방법
호출 가능 타입 문서의 목록에 모듈이 추가되며, __call__()에 대한 링크도 함께 제공됩니다. Emulating callable objects 문서에는 모듈 어트리뷰트 접근 사용자 정의 섹션과 비슷하게, 호출 가능 객체인 모듈을 예제 코드와 함께 다루는 섹션이 포함될 것입니다.
참조 구현
호출 가능 객체인 모듈의 제안된 구현은 CPython PR #103742에서 확인할 수 있습니다.
기각된 아이디어
__getattr__ 및 __dir__가 도입되었고 __call__을 사용할 수 있게 하자는 제안도 있으므로, __or__나 __iter__와 같은 모든 Special method names를 모듈에서 사용할 수 있게 할 가치가 있는지도 검토했습니다. 이것이 전혀 바람직하지 않은 것은 아니지만, 하위 호환성 문제가 생길 가능성이 커지며, 이러한 다른 특수 메서드들은 __call__에 비해 라이브러리 작성자에게 덜 유용할 가능성이 높습니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.