元类和__prepare__()

问题描述:

我在教我自己关于__prepare__函数。我看到这个片段在PEP3115元类和__prepare__()

# 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之间的链接?

非常感谢大家!

这是非常简单的,因为它准备做什么。

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

如果元类没有__prepare__属性,那么类 命名空间被初始化为一个空的有序映射。

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

这意味着,被传递到元类__new____init__方法classdict属性正是由__prepare__返回相同的对象。

该对象应该是一个映射实例,也就是说,一个对象的行为类似于字典,并且至少具有__setitem__方法。这个__setitem__方法被Python调用,用于在声明的类体内部设置的所有变量。

也就是说,对于没有自定义元类的普通类,变量被记录在一个字典(一个有序的字典,如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__功能仍然非常先进,因此必须考虑它的用途。

+0

感谢您的回答!还有一个小问题 - 我们能否确保'__prepare__'函数总是返回是字典或装饰字典类型(不是其他类型)的名称空间? – luoshao23

+0

“确定”是什么意思?你应该编写你的'__prepare__'并且知道它返回的结果。如果它不必像一个完整的映射对象那样工作 - 它只需要'__setitem__',并且mclass'__init__'中的代码知道如何处理它。 – jsbueno

+0

(如果您的问题已得到解答,请不要忘记接受答案) – jsbueno