모듈의 __getattr__


127

__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

2
나는 모든 상황에서 작동하기 때문에 슬픔의 대답으로 갈 것입니다 (조금 지저분하고 더 잘 할 수 있지만). Harvard S와 S Lott는 깔끔한 답을 가지고 있지만 실용적인 해결책은 아닙니다.
Matt Joiner

1
귀하의 경우에는 속성 액세스를 만드는 것도 아니므로 한 번에 두 가지 다른 것을 요청합니다. 그래서 중요한 질문은 당신이 원하는 것입니다. 당신이 원하는 마십시오 salutation글로벌 또는 로컬 네임 스페이스에 존재하거나 모듈에 도트 액세스 할 때 이름의 동적 조회를 원하는가 (위의 코드를 수행하려고하는 것입니다)? 그것은 두 가지 다른 것입니다.
Lennart Regebro 2010 년

흥미로운 질문입니다. 어떻게 생각해 냈습니까?
Chris Wesseling 2011 년


1
__getattr__on 모듈은 Python 3.7에서 지원됩니다
Fumito Hamamura

답변:


42

얼마 전 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
동적으로 모듈을 m = ModuleType("mod")만들고 설정하면 m.__getattr__ = lambda attr: return "foo"; 그러나, 내가 실행할 때 from mod import foo, 나는 얻을 TypeError: 'module' object is not iterable.
weberc2

@ weberc2 : 그렇게 만들고 m.__getattr__ = lambda attr: "foo", 모듈에 대한 항목을 sys.modules['mod'] = m. 이후에는 from mod import foo.
martineau

wim : 모듈 수준 속성을 갖는 것과 같이 동적으로 계산 된 값을 가져 와서 my_module.whatever이를 호출 하도록 작성할 수 있습니다 ( import my_module).
martineau

115

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

  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와 호환이 필요하지 않을 경우 생략 할 수 있습니다.


3
이것은 가져 오기 기계가이 해킹을 적극적으로 활성화하기 때문에 작동하며, 마지막 단계에서로드 한 후 실제 모듈을 sys.modules에서 가져옵니다 . 문서의 어딘가에 언급되어 있습니까?
Piotr Dobrogost

4
지금은 그것을 생각이 해킹을 사용하여 안심 더 느낌 "반 제재":
마크 Nunberg

3
이 만드는처럼 터져 일을하고있다 import sys주고를 None위해 sys. 이 해킹은 Python 2에서 승인되지 않은 것 같습니다.
asmeurer


1
@qarma : 파이썬 모듈이 클래스 상속 모델에 더 직접적으로 참여할 수 있도록 몇 가지 개선 사항이 언급 된 것을 기억하는 것 같지만이 방법은 여전히 ​​작동하며 지원됩니다.
Ethan Furman 2015

48

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

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__])

10
좋은 더러운 : D
매트 소목

작동 할 수도 있지만 작성자의 실제 문제에 대한 해결책은 아닐 수 있습니다.
DasIch

12
"작동 할 수 있음"및 "아마도 안 됨"은별로 도움이되지 않습니다. 해킹 / 트릭이지만 작동하며 질문이 제기 한 문제를 해결합니다.
Håvard S

6
이것은 모듈을 가져오고 그것에 존재하지 않는 속성에 액세스하는 다른 모듈 에서 작동하지만 여기의 실제 코드 예제에서는 작동하지 않습니다. globals () 액세스는 sys.modules를 거치지 않습니다.
Marius Gedminas 2010 년

5
불행히도 이것은 현재 모듈에서 작동하지 않거나 import *.
Matt Joiner

19

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

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

class A(object):
....

# The implicit global instance
a= A()

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

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

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


정말 야심 찬 경우 클래스를 만들고 모든 메서드를 반복하고 각 메서드에 대한 모듈 수준 함수를 만들 수 있습니다.
폴 피셔

@Paul Fisher : 문제에 따라 클래스가 이미 존재합니다. 클래스의 모든 메서드를 노출하는 것은 좋은 생각이 아닐 수 있습니다. 일반적으로 이러한 노출 된 방법은 "편리한"방법입니다. 모든 것이 암시 적 전역 인스턴스에 적합한 것은 아닙니다.
S.Lott

13

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

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


8

이건 끔찍하지만 ...

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")

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

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

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


2
나는 Håvard S의 대답이 훨씬 더 깔끔해 보이기 때문에 내 대답을 선호하지만 이것은 질문에 직접 대답합니다.
슬픔

이것은 결국 내가 갔던 것에 훨씬 더 가깝습니다. 약간 지저분하지만 동일한 모듈 내에서 globals ()와 올바르게 작동합니다.
Matt Joiner

1
이 대답은 "모듈의 정적으로 정의 된 속성에 존재하지 않는 함수를 호출 할 때"라는 질문이 아닌 것처럼 보입니다. 무조건 작동하고 가능한 모든 클래스 메서드를 추가하기 때문입니다. AddGlobalAttribute()모듈 수준이있을 때만 수행하는 모듈 래퍼 ( AttributeError@ Håvard S의 논리와 반대)를 사용하여 수정할 수 있습니다 . 기회가 있으면 OP가 이미이 답변을 수락 했음에도 불구하고 이것을 테스트하고 내 하이브리드 답변을 추가 할 것입니다.
martineau

내 이전 댓글을 업데이트합니다. 이제 NameError전역 (모듈) 네임 스페이스에 대한 예외 를 가로채는 것이 매우 어렵습니다 (불가능합니까?). 이 답변이 현재 전역 네임 스페이스에 찾은 모든 가능성에 대해 콜 러블을 추가하여 모든 가능한 경우를 미리 처리하는 이유를 설명합니다.
martineau

5

여기 내 자신의 겸손한 공헌이 있습니다. @ 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")

-3

클래스가있는 모듈 파일을 만듭니다. 모듈을 가져옵니다. getattr방금 가져온 모듈에서 실행하십시오 . __import__sys.modules를 사용하여 동적 가져 오기를 수행 하고 모듈에서 가져올 수 있습니다 .

모듈은 다음과 같습니다 some_module.py.

class Foo(object):
    pass

class Bar(object):
    pass

그리고 다른 모듈에서 :

import some_module

Foo = getattr(some_module, 'Foo')

동적으로 수행 :

import sys

__import__('some_module')
mod = sys.modules['some_module']
Foo = getattr(mod, 'Foo')

1
여기서 다른 질문에 답하고 있습니다.
Marius Gedminas 2010 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.