模块上的__getattr__

时间:2010-03-15 13:20:08

标签: python module python-3.x getattr attributeerror

如何在模块上的类上实现等效的__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

9 个答案:

答案 0 :(得分:106)

您遇到两个基本问题:

  1. __xxx__方法仅在类
  2. 上查找
  3. TypeError: can't set attributes of built-in/extension type 'module'
  4. (1)意味着任何解决方案都必须跟踪正在检查哪个模块,否则每个模块将具有实例替换行为; (2)意味着(1)甚至不可能......至少不是直接的。

    幸运的是,sys.modules对于那里的内容并不挑剔,所以包装器可以工作,但仅用于模块访问(即import somemodule; somemodule.salutation('world');对于同一模块访问,你几乎必须从替换中抽取方法使用类上的自定义方法(我喜欢使用globals())或使用泛型函数(例如已经列为答案的那些)将它们添加到.export() eiher。要记住一件事:如果包装器每次都在创建一个新实例,并且全局解决方案没有,那么你最终会产生微妙的不同行为。哦,你不能同时使用它们 - 它是一个或另一个。 / p>


    <强>更新

    来自Guido van Rossum

      

    实际上有一种偶尔使用和推荐的黑客攻击:a   模块可以定义具有所需功能的类,然后在   最后,在sys.modules中用该类的实例替换它自己   (或者在课堂上,如果你坚持,但这通常没那么有用)。   E.g:

    # module foo.py
    
    import sys
    
    class Foo:
        def funct1(self, <args>): <code>
        def funct2(self, <args>): <code>
    
    sys.modules[__name__] = Foo()
    
      

    这是有效的,因为进口机械正在积极地实现这一点   hack,并且最后一步将实际模块拉出来   sys.modules,加载后。 (这不是偶然的。黑客是   很久以前提出过,我们决定我们足够支持它了   进口机械。)

    因此,实现所需内容的既定方法是在模块中创建单个类,并且作为模块的最后一个行为将sys.modules[__name__]替换为您的类的实例 - 现在您可以使用__getattr__ / __setattr__根据需要。

    请注意,如果您使用此功能,则在进行__getattribute__分配时,模块中的任何其他内容(例如全局变量,其他函数等)都将丢失 - 因此请确保所需内容仅在替换内部类。

答案 1 :(得分:44)

这是一个hack,但你可以用一个类包装模块:

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

答案 2 :(得分:21)

A while ago, Guido declared that all special method lookups on new-style classes bypass __getattr__ and __getattribute__。 Dunder方法以前曾在模块上工作过 - 例如,您可以在模糊broke之前简单地通过定义__enter____exit__来使用模块作为上下文管理器。

最近一些历史功能卷土重来,其中包含模块__getattr__,因此不再需要现有的hack(在导入时用sys.modules中的类替换自己的模块)

在Python 3.7+中,您只需使用一种显而易见的方法。要自定义模块上的属性访问权限,请在模块级别定义__getattr__函数,该函数应接受一个参数(属性名称),并返回计算值或引发AttributeError

# my_module.py

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

这也将允许钩子进入&#34;来自&#34;导入,即您可以为from my_module import whatever等语句返回动态生成的对象。

在相关说明中,与模块getattr一起,您还可以在模块级别定义__dir__函数以响应dir(my_module)。有关详细信息,请参阅PEP 562

答案 3 :(得分:19)

我们通常不这样做。

我们做的是这个。

class A(object):
....

# The implicit global instance
a= A()

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

为什么呢?这样隐式全局实例就可见了。

例如,查看random模块,它创建一个隐式全局实例,以略微简化您想要“简单”随机数生成器的用例。

答案 4 :(得分:13)

类似于@HåvardS提出的,在我需要在模块上实现一些魔法的情况下(比如__getattr__),我会定义一个继承自types.ModuleType的新类并将其放入在sys.modules中(可能会替换定义了我的自定义ModuleType的模块)。

请参阅__init__.py的主Werkzeug文件,以获得相当强大的实现。

答案 5 :(得分:7)

这是hackish,但是......

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

这通过迭代全局命名空间中的所有对象来工作。如果该项是一个类,它将迭代类属性。如果属性是可调用的,则将其作为函数添加到全局命名空间。

它忽略包含“__”的所有属性。

我不会在生产代码中使用它,但它应该让你开始。

答案 6 :(得分:4)

这是我自己的谦逊贡献 - 略微修饰了@HåvardS的高评价答案,但更加明确(因此@ S.Lott可能会接受,尽管可能对OP来说不够好):< / p>

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

答案 7 :(得分:-2)

创建包含您的类的模块文件。导入模块。在刚刚导入的模块上运行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')

答案 8 :(得分:-2)

在某些情况下,globals()字典就足够了,例如,您可以从全局范围中按名称实例化一个类:

from somemodule import * # imports SomeClass

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