模块上的__getattr__

Mat*_*ner 114 python module attributeerror getattr python-3.x

如何__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")
Run Code Online (Sandbox Code Playgroud)

这使:

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
Run Code Online (Sandbox Code Playgroud)

Eth*_*man 108

您遇到两个基本问题:

  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:

实际上有一个偶尔使用和推荐的hack:一个模块可以定义一个具有所需功能的类,然后最后在sys.modules中用该类的实例替换它自己(或者如果你坚持的话,用类替换它,但这通常不太有用).例如:

# module foo.py

import sys

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

sys.modules[__name__] = Foo()
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为导入机制正在积极地启用此hack,并且在加载后,最后一步将实际模块从sys.modules中拉出.(这不是偶然的.很久以前就提出了黑客攻击,我们决定在进口机械中足够支持它.)

因此,建立的方式来完成你想要的是你的模块中创建一个类,并作为该模块的最后一幕替换sys.modules[__name__]您的类的实例-现在你可以玩__getattr__/ __setattr__/ __getattribute__需要.

请注意,如果使用此功能,模块中的任何其他内容(如全局变量,其他函数等)将在sys.modules分配时丢失- 因此请确保所需的所有内容都在替换类中.

  • 现在我觉得使用这个hack更加放心,考虑到它"半制裁":) (4认同)
  • *这是有效的,因为导入机制正在积极地启用这个hack,并且最后一步是将实际模块从sys.modules中拉出来,加载后*它是否在文档中的某处提到过? (3认同)
  • 这样做很棘手,比如让`import sys`为`sys`提供`None`.我猜这个黑客在Python 2中没有得到批准. (3认同)
  • @asmeurer:要了解其原因(和解决方案),请参阅问题[为什么在分配给sys.modules\[\ _\_ _ name __ \]后,\ _ _ _ name__的值会发生变化?](http:// stackoverflow .COM /问题/ 5365562 /为什么 - 是最值的名称变化,后分配到SYS模块名称). (3认同)
  • @弗里德里希:是的。检查我更新的答案中的**注释2**。 (2认同)

Håv*_*d S 47

这是一个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__])
Run Code Online (Sandbox Code Playgroud)

  • "可能工作"和"可能不工作"不是很有帮助.这是一个黑客/技巧,但它有效,并解决了问题带来的问题. (12认同)
  • 好又脏:D (10认同)
  • 虽然这将在导入模块并访问其中不存在的属性的_other_模块中起作用,但它不适用于此处的实际代码示例.访问globals()不会通过sys.modules. (6认同)
  • 不幸的是,这对于当前模块不起作用,或者可能在`import*`之后访问的东西. (5认同)

wim*_*wim 27

不久之前,Guido宣称所有关于新式类的特殊方法查找都会绕过__getattr____getattribute__.Dunder方法曾经工作的模块-你可以,例如,使用一个模块作为一个上下文管理器简单地通过定义__enter____exit__,这些技巧之前爆发.

最近一些历史特征已经卷土重来,其中的模块__getattr__,所以现在的黑客攻击(一个模块sys.modules在导入时替换为一个类)应该不再需要了.

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

# my_module.py

def __getattr__(name: str) -> Any:
    ...
Run Code Online (Sandbox Code Playgroud)

这也允许挂钩进入"from"导入,即你可以为语句返回动态生成的对象from my_module import whatever.

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

  • 随着时间的流逝和语言的更新,OP实际上改变了接受的答案?我在正确的地点吗? (4认同)

S.L*_*ott 19

我们通常不这样做.

我们做的是这个.

class A(object):
....

# The implicit global instance
a= A()

def salutation( *arg, **kw ):
    a.salutation( *arg, **kw )
Run Code Online (Sandbox Code Playgroud)

为什么?这样隐式全局实例是可见的.

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


Mat*_*son 13

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

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


gri*_*eve 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")
Run Code Online (Sandbox Code Playgroud)

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

它忽略包含"__"的所有属性.

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

  • 我更喜欢HåvardS对我的回答,因为它看起来更干净,但这直接回答了问题. (2认同)

mar*_*eau 5

这是我自己的微不足道的贡献——对@Håvard S 的高评价答案略加修饰,但更明确一些(因此 @S.Lott 可能可以接受,即使可能对 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")
Run Code Online (Sandbox Code Playgroud)