元类和__prepare __()

时间:2017-10-19 10:21:09

标签: python metaclass

我正在教自己__prepare__功能。我在PEP3115

看到了这个片段
# The custom dictionary
class member_table(dict):
    def __init__(self):
        self.member_names = []

    def __setitem__(self, key, value):
        # if the key is not already defined, add to the
        # list of keys.
        if key not in self:
            self.member_names.append(key)

        # Call superclass
        dict.__setitem__(self, key, value)

# The metaclass
class OrderedClass(type):

    # The prepare function
    @classmethod
    def __prepare__(metacls, name, bases): # No keywords in this case
        return member_table()

    # The metaclass invocation
    def __new__(cls, name, bases, classdict):
        # Note that we replace the classdict with a regular
        # dict before passing it to the superclass, so that we
        # don't continue to record member names after the class
        # has been created.
        result = type.__new__(cls, name, bases, dict(classdict))
        result.member_names = classdict.member_names
        return result

class MyClass(metaclass=OrderedClass):
    # method1 goes in array element 0
    def method1(self):
        pass

    # method2 goes in array element 1
    def method2(self):
        pass

我的问题是在这一行:     result.member_names = classdict.member_names

变量classdict如何从member_table类中获取属性?我看到__prepare__函数返回了member_table的实例,但member_table()classdict.member_names之间的链接是如何生成的?

非常感谢你们所有人!

1 个答案:

答案 0 :(得分:4)

这非常简单,因为它正是准备所做的。

  

3.3.3.3。准备类命名空间一旦识别出适当的元类,就会准备类命名空间。如果   元类具有__prepare__属性,它被称为namespace = metaclass.__prepare__(name, bases, **kwds)(其中附加   关键字参数,如果有的话,来自类定义)。

     

如果元类没有__prepare__属性,那么该类   namespace被初始化为空的有序映射。

https://docs.python.org/3/reference/datamodel.html#preparing-the-class-namespace

这意味着,传递到元类classdict__new__方法的__init__属性与__prepare__返回的对象完全相同。

该对象应该是一个映射实例,即一个行为类似于dict且至少具有__setitem__方法的对象。 Python为在声明的类体本身内设置的所有变量调用此__setitem__方法。

也就是说,对于没有自定义元类的普通类,变量记录在字典中(有序字典,从Python 3.6开始)。

当Python在类体内运行每个语句时会发生这种情况。如果在类体内调用locals(),则返回相同的对象:

In [21]: class M(type):
    ...:     def __prepare__(self, *args):
    ...:         class CustomDict(dict):
    ...:             __repr__ = lambda self: "I am a custom dict: " + str(id(self))
    ...:         namespace = CustomDict()
    ...:         print("From __prepare__", namespace)
    ...:         return namespace
    ...: 
    ...:     def __new__(metacls, name, bases, namespace):
    ...:         print("From __new__:", namespace)
    ...:         return super().__new__(metacls, name, bases, namespace)
    ...:     
    ...:     

In [22]: class Test(metaclass=M):
    ...:     def __init__(self):
    ...:         ...
    ...:     print("From class body:", locals(), locals()["__init__"])
    ...:     
    ...:     
From __prepare__ I am a custom dict: 140560887720440
From class body: I am a custom dict: 140560887720440 <function Test.__init__ at 0x7fd6e1bd7158>
From __new__: I am a custom dict: 140560887720440

首次设计此功能时的主要用例可能正是使类体内的声明顺序有意义的可能性。也就是说,__prepare__方法只能返回collections.OrderedDict个实例,而__new____init__会对该订单起作用。从Python 3.6开始,默认情况下对类属性进行排序 - 而__prepare__功能仍然非常先进,实际上必须考虑它的用途。