代码之家  ›  专栏  ›  技术社区  ›  Temperosa

元类init中setattr()的意外行为__

  •  -1
  • Temperosa  · 技术社区  · 7 年前

    这里有一个基本的 Metaclass 打算提供一种习俗 __init__ :

    class D(type):
        def __init__(cls, name, bases, attributes):
            super(D, cls).__init__(name, bases, attributes)
            for hook in R.registered_hooks:
                setattr(cls, hook.__qualname__, hook)
    

    for循环遍历函数列表,调用 setattr(cls, hook.__qualname__, hook) 每一个都是。

    下面是一个使用上述元类的类:

    class E(metaclass=D):
        def __init__(self):
            pass
    

    奇怪的是:

    E().__dict__   prints {}
    

    但当我打电话

    `E.__dict__   prints {'f1': <function f1 at 0x001>, 'f2': <function f2 at 0x002>}`
    

    我希望将这些函数作为属性添加到 实例属性 ,因为 爱因斯坦 为类提供自定义初始化,但似乎属性已添加到 类属性 .

    这是什么原因?在这种情况下,如何向实例添加属性?谢谢!

    3 回复  |  直到 7 年前
        1
  •  3
  •   chepner    7 年前

    它们作为实例属性添加。不过,例子是 ,作为元类的实例。你没有定义 __init__ 方法 E ;您定义的方法 type 用于初始化 e 本身。

    如果只想将属性添加到 e ,您在错误的级别上工作;您应该定义一个mixin并为此使用多重继承。

        2
  •  2
  •   Olivier Melançon iacob    7 年前

    为了 __init__ 在对象实例化时调用 obj ,您需要定义 type(obj).__init__ . 通过使用元类,您定义了 type(type(obj)).__init__ . 你的工作水平不对。

    在这种情况下,您只需要继承,而不需要元类。

    class D():
        def __init__(self, *args):
            print('D.__init__ was called')
    
    class E(D):
        pass
    
    e = E() # prints: D.__init__ was called
    

    注意你仍然会发现 e.__dict__ 是空的。

    print(e.__dict__) # {}
    

    这是因为您的实例没有属性,方法在类中存储和查找。

        3
  •  2
  •   Grant Palmer    7 年前

    如果使用的是元类,则意味着要更改类的定义,这会间接影响该类实例的属性。假设你真的想这么做:

    你正在创建一个自定义 __init__ 方法。和普通的实例化一样, 爱因斯坦 对已创建的对象调用。通常,当您对元类执行一些有意义的操作时,您需要在实例化之前添加到类的dict中,这是在 __new__ 方法以避免子类化中可能出现的问题。应该是这样的:

    class D(type):
    
        def __new__(cls, name, bases, dct):
            for hook in R.registered_hooks:
                dct[hook.__qualname__] = hook
            return super(D, cls).__new__(cls, name, bases, dct)
    

    这不会修改 __dict__ 类的实例,但实例的属性解析也会查找类属性。所以如果你加上 foo 作为 E 在元类中, E().foo 将返回预期值,即使在 E().__dict__