[python] 모듈의 __getattr__

__getattr__모듈에서 클래스 와 동등한 것을 어떻게 구현할 수 있습니까?

모듈의 정적으로 정의 된 속성에 존재하지 않는 함수를 호출 할 때 해당 모듈에 클래스의 인스턴스를 생성하고 모듈의 속성 조회에서 실패한 것과 동일한 이름으로 메서드를 호출하고 싶습니다.

class A(object):
    def salutation(self, accusative):
        print "hello", accusative

# note this function is intentionally on the module, and not the class above
def __getattr__(mod, name):
    return getattr(A(), name)

if __name__ == "__main__":
    # i hope here to have my __getattr__ function above invoked, since
    # salutation does not exist in the current namespace
    salutation("world")

다음을 제공합니다.

matt@stanley:~/Desktop$ python getattrmod.py
Traceback (most recent call last):
  File "getattrmod.py", line 9, in <module>
    salutation("world")
NameError: name 'salutation' is not defined



답변

얼마 전 Guido는 새로운 스타일의 클래스에 대한 모든 특수 메서드 조회가 __getattr____getattribute__ . Dunder 메소드는 이전에 모듈에서 작업 했었습니다. 예를 들어, 그 트릭 깨지기 전에 간단히 __enter__and 를 정의하여 모듈을 컨텍스트 관리자로 사용할 수 있습니다 .__exit__

최근 몇 가지 역사적 기능이 컴백했고, 그 __getattr__중 모듈 은 기존 해킹 ( sys.modules임포트시 클래스로 대체되는 모듈 )이 더 이상 필요하지 않습니다.

Python 3.7 이상에서는 한 가지 분명한 방법을 사용합니다. 모듈에 대한 속성 액세스를 사용자 정의하려면 __getattr__하나의 인수 (속성 이름)를 허용해야하는 모듈 수준에서 함수를 정의 하고 계산 된 값을 반환하거나 다음을 발생시킵니다 AttributeError.

# my_module.py

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

이것은 또한 “from”가져 오기에 대한 후크를 허용합니다. 즉, from my_module import whatever.

관련 메모에서 모듈 getattr과 함께 __dir__에 응답 할 모듈 수준에서 함수를 정의 할 수도 있습니다 dir(my_module). 자세한 내용은 PEP 562 를 참조하십시오.


답변

여기에는 두 가지 기본 문제가 있습니다.

  1. __xxx__ 메서드는 클래스에서만 조회됩니다.
  2. TypeError: can't set attributes of built-in/extension type 'module'

(1) 어떤 솔루션이 어떤 모듈이 검사되고 있는지 추적해야 함을 의미합니다. 그렇지 않으면 모든 모듈이 인스턴스 대체 동작을 갖게됩니다. 그리고 (2)는 (1)이 불가능하다는 것을 의미합니다 … 적어도 직접적으로는 아닙니다.

다행스럽게도 sys.modules는 거기에가는 것에 대해 까다 롭지 않으므로 래퍼가 작동하지만 모듈 액세스에만 해당됩니다 (예 : import somemodule; somemodule.salutation('world')동일한 모듈 액세스의 경우 대체 클래스에서 메서드를 잡아 globals()당기고 다음을 사용하여 eiher에 추가해야합니다) . 클래스에 대한 사용자 지정 메서드 (사용 .export()하는 것을 좋아함) 또는 일반 함수 (예 : 이미 답변으로 나열 됨)와 함께. 명심해야 할 한 가지 : 래퍼가 매번 새 인스턴스를 만들고 전역 솔루션이 그렇지 않은 경우 당신은 미묘하게 다른 행동을하게됩니다.


최신 정보

에서 귀도 반 로섬 (Guido van Rossum) :

실제로 가끔 사용 및 권장되는 해킹이 있습니다. 모듈은 원하는 기능을 가진 클래스를 정의한 다음 마지막에 sys.modules에서 해당 클래스의 인스턴스로 자신을 대체 할 수 있습니다. ,하지만 일반적으로 유용하지 않음). 예 :

# module foo.py

import sys

class Foo:
    def funct1(self, <args>): <code>
    def funct2(self, <args>): <code>

sys.modules[__name__] = Foo()

이는 가져 오기 기계가이 해킹을 적극적으로 활성화하고 최종 단계에서 실제 모듈을로드 한 후 sys.modules에서 꺼내기 때문에 작동합니다. (이것은 우연이 아닙니다. 해킹은 오래 전에 제안되었으며 우리는 수입 기계에서 충분히 지원할 수 있다고 판단했습니다.)

따라서 원하는 것을 달성하기위한 확립 된 방법은 모듈에 단일 클래스를 생성하고 모듈의 마지막 동작이 sys.modules[__name__]클래스의 인스턴스로 대체되는 것 입니다. 이제 필요에 따라 __getattr__/ __setattr__/ __getattribute__로 플레이 할 수 있습니다 .


참고 1 :이 기능을 사용하면 전역, 기타 기능 등과 같은 모듈의 다른 sys.modules모든 것이 할당 될 때 손실 되므로 필요한 모든 것이 대체 클래스 내에 있는지 확인하십시오.

참고 2 : 지원하려면 클래스에서 정의 from module import *해야합니다 __all__. 예를 들면 :

class Foo:
    def funct1(self, <args>): <code>
    def funct2(self, <args>): <code>
    __all__ = list(set(vars().keys()) - {'__module__', '__qualname__'})

Python 버전에 따라에서 생략 할 다른 이름이있을 수 있습니다 __all__. 는 set()파이썬 2와 호환이 필요하지 않을 경우 생략 할 수 있습니다.


답변

이것은 해킹이지만 모듈을 클래스로 래핑 할 수 있습니다.

class Wrapper(object):
  def __init__(self, wrapped):
    self.wrapped = wrapped
  def __getattr__(self, name):
    # Perform custom logic here
    try:
      return getattr(self.wrapped, name)
    except AttributeError:
      return 'default' # Some sensible default

sys.modules[__name__] = Wrapper(sys.modules[__name__])


답변

우리는 보통 그렇게하지 않습니다.

우리가하는 일은 이것입니다.

class A(object):
....

# The implicit global instance
a= A()

def salutation( *arg, **kw ):
    a.salutation( *arg, **kw )

왜? 암시 적 전역 인스턴스가 표시되도록합니다.

예를 들어, random“간단한”난수 생성기를 원하는 사용 사례를 약간 단순화하기 위해 암시 적 전역 인스턴스를 생성하는 모듈을 살펴보십시오 .


답변

@ Håvard S가 제안한 것과 유사하게, 모듈에 마법을 구현해야하는 경우 (예 :), __getattr__상속 types.ModuleType하고 넣는 새 클래스를 정의 합니다.sys.modules (아마도 내 사용자 지정ModuleType 가 정의 된 ).

이것 의 상당히 강력한 구현은 Werkzeug 의 메인 __init__.py파일을 참조하십시오 .


답변

이건 끔찍하지만 …

import types

class A(object):
    def salutation(self, accusative):
        print "hello", accusative

    def farewell(self, greeting, accusative):
         print greeting, accusative

def AddGlobalAttribute(classname, methodname):
    print "Adding " + classname + "." + methodname + "()"
    def genericFunction(*args):
        return globals()[classname]().__getattribute__(methodname)(*args)
    globals()[methodname] = genericFunction

# set up the global namespace

x = 0   # X and Y are here to add them implicitly to globals, so
y = 0   # globals does not change as we iterate over it.

toAdd = []

def isCallableMethod(classname, methodname):
    someclass = globals()[classname]()
    something = someclass.__getattribute__(methodname)
    return callable(something)


for x in globals():
    print "Looking at", x
    if isinstance(globals()[x], (types.ClassType, type)):
        print "Found Class:", x
        for y in dir(globals()[x]):
            if y.find("__") == -1: # hack to ignore default methods
                if isCallableMethod(x,y):
                    if y not in globals(): # don't override existing global names
                        toAdd.append((x,y))


for x in toAdd:
    AddGlobalAttribute(*x)


if __name__ == "__main__":
    salutation("world")
    farewell("goodbye", "world")

이는 전역 네임 스페이스의 모든 개체를 반복하여 작동합니다. 항목이 클래스 인 경우 클래스 속성을 반복합니다. 속성이 호출 가능하면 전역 네임 스페이스에 함수로 추가됩니다.

“__”를 포함하는 모든 속성을 무시합니다.

나는 이것을 프로덕션 코드에서 사용하지 않을 것이지만 시작해야합니다.


답변

여기 내 자신의 겸손한 공헌이 있습니다. @ Håvard S의 높은 평가를받은 답변을 약간 꾸미는 것이지만 좀 더 명시 적입니다 (따라서 OP에는 충분하지 않더라도 @ S.Lott가 받아 들일 수 있습니다).

import sys

class A(object):
    def salutation(self, accusative):
        print "hello", accusative

class Wrapper(object):
    def __init__(self, wrapped):
        self.wrapped = wrapped

    def __getattr__(self, name):
        try:
            return getattr(self.wrapped, name)
        except AttributeError:
            return getattr(A(), name)

_globals = sys.modules[__name__] = Wrapper(sys.modules[__name__])

if __name__ == "__main__":
    _globals.salutation("world")