首页 文章

模块上的__getattr__

提问于
浏览
105

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

  • 18

    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:
        ...
    

    这也将允许挂钩进入"from"导入,即您可以返回动态生成的对象,例如 from my_module import whatever .

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

  • 104

    您遇到两个基本问题:

    • __xxx__ 方法只在类上查找

    • TypeError: can't set attributes of built-in/extension type 'module'

    (1)意味着任何解决方案都必须跟踪正在检查的模块,否则每个模块都将具有实例替换行为; (2)意味着(1)甚至不可能......至少不是直接的 .

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


    Update

    来自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()
    

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

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

  • -2

    这是一个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

    我们通常不这样做 .

    我们做的是这个 .

    class A(object):
    ....
    
    # The implicit global instance
    a= A()
    
    def salutation( *arg, **kw ):
        a.salutation( *arg, **kw )
    

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

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

  • 7

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

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

  • 4

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

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

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

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

  • 15

    这是我自己的谦逊贡献 - @HåvardS的轻微修饰额定答案,但更明确一点(所以@ 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")
    
  • 13

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

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

    from somemodule import * # imports SomeClass
    
    someclass_instance = globals()['SomeClass']()
    

相关问题