programing tip

모듈의 __getattr__

itbloger 2020. 7. 27. 07:39
반응형

모듈의 __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

얼마 전, 귀도는 우회 새로운 스타일의 클래스에있는 모든 특별한 방법 조회 선언 __getattr__하고__getattribute__ . 던더 방법은 이전에 모듈에서 일했다 - 당신은, 예를 들어, 단순히 정의하여 상황에 맞는 매니저로 모듈을 사용할 수 있습니다 __enter____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()합니다) 클래스의 사용자 정의 메소드 (을 사용 .export()하는 것을 좋아함) 또는 일반 함수 (예 : 이미 답변으로 나열 된 함수)를 사용하십시오. 두 래퍼가 매번 새 인스턴스를 작성하고 전역 솔루션이 그렇지 않은 경우, 당신은 미묘하게 다른 행동으로 끝납니다. 오, 그리고 둘 다 동시에 사용할 수는 없습니다.


최신 정보

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

There is actually a hack that is occasionally used and recommended: a module can define a class with the desired functionality, and then at the end, replace itself in sys.modules with an instance of that class (or with the class, if you insist, but that's generally less useful). E.g.:

# module foo.py

import sys

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

sys.modules[__name__] = Foo()

This works because the import machinery is actively enabling this hack, and as its final step pulls the actual module out of sys.modules, after loading it. (This is no accident. The hack was proposed long ago and we decided we liked enough to support it in the import machinery.)

So the established way to accomplish what you want is to create a single class in your module, and as the last act of the module replace sys.modules[__name__] with an instance of your class -- and now you can play with __getattr__/__setattr__/__getattribute__ as needed.

Note that if you use this functionality anything else in the module, such as globals, other functions, etc., will be lost when the sys.modules assignment is made -- so make sure everything needed is inside the replacement class.


This is a hack, but you can wrap the module with a class:

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

We don't usually do it that way.

What we do is this.

class A(object):
....

# The implicit global instance
a= A()

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

Why? So that the implicit global instance is visible.

For examples, look at the random module, which creates an implicit global instance to slightly simplify the use cases where you want a "simple" random number generator.


Similar to what @Håvard S proposed, in a case where I needed to implement some magic on a module (like __getattr__), I would define a new class that inherits from types.ModuleType and put that in sys.modules (probably replacing the module where my custom ModuleType was defined).

See the main __init__.py file of Werkzeug for a fairly robust implementation of this.


This is hackish, but...

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

This works by iterating over the all the objects in the global namespace. If the item is a class, it iterates over the class attributes. If the attribute is callable it adds it to the global namespace as a function.

It ignore all attributes which contain "__".

I wouldn't use this in production code, but it should get you started.


Here's my own humble contribution -- a slight embellishment of @Håvard S's highly rated answer, but a bit more explicit (so it might be acceptable to @S.Lott, even though probably not good enough for the OP):

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

Create your module file that has your classes. Import the module. Run getattr on the module you just imported. You can do a dynamic import using __import__ and pull the module from sys.modules.

Here's your module some_module.py:

class Foo(object):
    pass

class Bar(object):
    pass

And in another module:

import some_module

Foo = getattr(some_module, 'Foo')

Doing this dynamically:

import sys

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

In some circumstances the globals() dictionary can suffice, for example you can instantiate a class by name from the global scope:

from somemodule import * # imports SomeClass

someclass_instance = globals()['SomeClass']()

참고URL : https://stackoverflow.com/questions/2447353/getattr-on-a-module

반응형