代码之家  ›  专栏  ›  技术社区  ›  Matt Joiner

_在模块上获取属性

  •  107
  • Matt Joiner  · 技术社区  · 14 年前

    如何实现 __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 回复  |  直到 6 年前
        1
  •  19
  •   wim    6 年前

    A while ago, Guido declared that all special method lookups on new-style classes bypass __getattr__ and __getattribute__ . Dunder方法以前曾在模块上工作过——例如,您可以通过定义一个模块作为上下文管理器 __enter__ __exit__ 在这些把戏之前 broke .

    最近,一些历史特征又出现了,模块 _格塔特__ 其中,现有的黑客(一个模块用一个 sys.modules 在进口时)应该不再需要。

    在Python3.7+中,您只需使用一种明显的方法。要自定义模块上的属性访问,请定义 _格塔特__ 模块级的函数,该函数应接受一个参数(属性名),并返回计算值或引发 AttributeError :

    # my_module.py
    
    def __getattr__(name: str) -> Any:
        ...
    

    这也将允许钩住“from”导入,即可以为语句返回动态生成的对象,例如 from my_module import whatever .

    在相关注释中,除了模块getattr,您还可以定义 __dir__ 响应模块级功能 dir(my_module) . 见 PEP 562 有关详细信息。

        2
  •  105
  •   Ethan Furman    6 年前

    这里有两个基本问题:

    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 :

    实际上,偶尔会使用和推荐一个黑客:a 模块可以定义具有所需功能的类,然后在 最后,用该类的实例替换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[__name__] 用你的一个例子——现在你可以玩 __getattr__ / __setattr__ / __getattribute__ 根据需要。

    请注意,如果使用此功能,模块中的任何其他功能(如全局函数、其他函数等)将在 sys.modules 分配完成了——所以确保所需的一切都在替换类中。

        3
  •  44
  •   Håvard S    14 年前

    这是一个黑客,但您可以用类包装模块:

    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__])
    
        4
  •  18
  •   S.Lott    14 年前

    我们通常不会那样做。

    我们要做的就是这个。

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

    为什么?使隐式全局实例可见。

    例如,请看 random 模块,它创建一个隐式全局实例来稍微简化需要“简单”随机数生成器的用例。

        5
  •  13
  •   suzanshakya    12 年前

    类似于@h_?vard的提议,在这种情况下,我需要在模块上实现一些魔力(例如 __getattr__ ,我将定义一个继承自 types.ModuleType 把它放进去 sys.modules (可能在我的自定义模块中替换模块 ModuleType 已定义)。

    查看主 __init__.py 文件 Werkzeug 以实现这一点。

        6
  •  7
  •   grieve    14 年前

    这有点陈词滥调,但是…

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

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

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

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

        7
  •  4
  •   martineau    11 年前

    以下是我自己谦虚的贡献,@h vard高度评价的答案稍加润色,但更明确一点(因此@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")
    
        8
  •  -2
  •   drr    14 年前

    创建包含类的模块文件。导入模块。跑 getattr 在刚导入的模块上。您可以使用 __import__ 从系统模块中拉出模块。

    这是你的模块 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')
    
        9
  •  -2
  •   James    13 年前

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

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