Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 562 – 모듈 __getattr__과 __dir__

Author:
Ivan Levkivskyi <levkivskyi at gmail.com>
Status:
Final
Type:
Standards Track
Created:
09-Sep-2017
Python-Version:
3.7
Post-History:
09-Sep-2017
Resolution:
Python-Dev message

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

Important

This PEP is a historical document. The up-to-date, canonical documentation can now be found at Customizing Module Attribute Access.

×

See PEP 1 for how to propose changes.

개요

모듈 속성 접근에 대한 기본적인 사용자 정의를 제공하기 위해, 모듈에 정의된 __getattr____dir__ 함수를 지원하는 것을 제안합니다.

근거

모듈 속성에 대한 접근을 사용자 정의하거나 그 밖의 방법으로 제어할 수 있으면 편리한 경우가 있습니다. 대표적인 예로 폐지 경고(deprecation warning)를 관리하는 경우가 있습니다. 일반적인 임시방편은 모듈 객체의 __class__types.ModuleType의 사용자 정의 서브클래스로 대입하거나, sys.modules 항목을 사용자 정의 래퍼 인스턴스로 교체하는 것입니다. 모듈에 직접 정의된 __getattr__을 인식하여, 이것이 일반적인 __getattr__ 메서드처럼 동작하되 모듈 인스턴스에 정의된다는 점만 다르게 함으로써 이 절차를 단순화하면 편리할 것입니다. 예를 들면:

# lib.py

from warnings import warn

deprecated_names = ["old_function", ...]

def _deprecated_old_function(arg, other):
    ...

def __getattr__(name):
    if name in deprecated_names:
        warn(f"{name} is deprecated", DeprecationWarning)
        return globals()[f"_deprecated_{name}"]
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

# main.py

from lib import old_function  # Works, but emits the warning

__getattr__의 또 다른 널리 쓰이는 사용 사례는 지연된 서브모듈 임포트일 것입니다. 간단한 예를 살펴봅시다.:

# lib/__init__.py

import importlib

__all__ = ['submod', ...]

def __getattr__(name):
    if name in __all__:
        return importlib.import_module("." + name, __name__)
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

# lib/submod.py

print("Submodule loaded")
class HeavyClass:
    ...

# main.py

import lib
lib.submod.HeavyClass  # prints "Submodule loaded"

유사한 기능을 위해 인스턴스 프로퍼티를 지원하는 것을 제안하는 관련 제안 PEP 549가 있습니다. 차이점은 이 PEP가 더 빠르고 단순한 메커니즘을 제안하지만, 더 기본적인 수준의 사용자 정의만 제공한다는 것입니다. 이 제안의 추가적인 동기는 PEP 484가 이미 파이썬 스텁 파일에서 이러한 목적으로 모듈 __getattr__을 사용하는 것을 정의하고 있다는 점입니다. PEP 484를 참고하십시오.

또한, 모듈에 대한 dir() 호출 결과를 수정하여 폐지된 속성이나 그 밖의 동적으로 생성된 속성을 보여줄 수 있도록, 모듈 수준의 __dir__ 함수를 지원하는 것을 제안합니다. 예를 들면:

# lib.py

deprecated_names = ["old_function", ...]
__all__ = ["new_function_one", "new_function_two", ...]

def new_function_one(arg, other):
   ...
def new_function_two(arg, other):
    ...

def __dir__():
    return sorted(__all__ + deprecated_names)

# main.py

import lib

dir(lib)  # prints ["new_function_one", "new_function_two", "old_function", ...]

명세

모듈 수준의 __getattr__ 함수는 어트리뷰트의 이름인 하나의 인자를 받아들여야 하며, 계산된 값을 반환하거나 AttributeError를 발생시켜야 합니다.:

def __getattr__(name: str) -> Any: ...

정상적인 조회(즉, object.__getattribute__)를 통해 모듈 객체에서 어트리뷰트를 찾지 못하면, AttributeError를 발생시키기 전에 모듈 __dict__에서 __getattr__을 찾습니다. 찾으면, 어트리뷰트 이름과 함께 호출되고 그 결과가 반환됩니다. 이름을 모듈 전역으로 조회하면 모듈 __getattr__을 우회합니다. 이는 의도된 것으로, 그렇지 않으면 내장 객체에 대해 __getattr__을 호출하는 것이 성능을 크게 해칠 것입니다.

__dir__ 함수는 인자를 받지 않아야 하며, 모듈에서 접근 가능한 이름들을 나타내는 문자열 리스트를 반환해야 합니다.:

def __dir__() -> List[str]: ...

존재하는 경우, 이 함수는 모듈에 대한 표준 dir() 검색을 재정의합니다.

이 PEP의 참조 구현은 [2]에서 찾을 수 있습니다.

하위 호환성과 성능에 미치는 영향

이 PEP는 모듈 수준(전역) 이름 __getattr____dir__을 사용하는 코드를 깨뜨릴 수 있습니다. (하지만 언어 참조는 문서화되지 않은 모든 던더 이름을 명시적으로 예약해 두고 있으며, “경고 없는 손상”을 허용합니다. [3]을 참조하십시오.) 이 PEP의 성능상 영향은 미미한데, __getattr__은 어트리뷰트가 없을 때만 호출되기 때문입니다.

모듈 어트리뷰트 검색을 수행하는 일부 도구는 __getattr__을 예상하지 못할 수 있습니다. 그러나 이 문제는 새로운 것이 아닌데, 재정의된 __getattr____dir__을 가진 모듈 서브클래스로 모듈을 대체하는 것이 이미 가능하기 때문입니다. 다만 이 PEP로 인해 그러한 문제가 더 자주 발생할 수 있습니다.

논의

모듈 __getattr__의 사용은 참조된 객체가 피클 가능한 상태를 유지하도록 주의가 필요하다는 점에 유의하십시오. 예를 들어, 함수의 __name__ 어트리뷰트는 __getattr__을 통해 접근 가능한 이름과 일치해야 합니다.:

def keep_pickleable(func):
    func.__name__ = func.__name__.replace('_deprecated_', '')
    func.__qualname__ = func.__qualname__.replace('_deprecated_', '')
    return func

@keep_pickleable
def _deprecated_old_function(arg, other):
    ...

클래스 수준 __getattr__에서 하듯이 재귀를 피하도록 주의해야 합니다.

__getattr__ 트리거 없이 모듈 전역을 사용하려면(예를 들어 지연 로드되는 서브모듈을 사용하고자 하는 경우) 다음과 같이 접근할 수 있습니다:

sys.modules[__name__].some_global

또는 다음과 같이:

from . import some_global

후자는 모듈 속성을 설정하므로 __getattr__이 한 번만 호출된다는 점에 유의하십시오.

참고 자료