为什么类定义的metaclass关键字参数接受可调用?

时间:2016-10-13 19:54:43

标签: python class python-3.x metaclass

背景

Python 3 documentation清楚地描述了如何确定类的元类:

  
      
  • 如果没有给出基数且没有给出明确的元类,则使用类型()
  •   
  • 如果给出了显式元类并且它不是type()的实例,那么它将直接用作元类
  •   
  • 如果给出了类型()的实例作为显式元类,或者定义了基数,则使用最派生的元类
  •   

因此,根据第二条规则,可以使用callable指定元类。如,

class MyMetaclass(type):
    pass

def metaclass_callable(name, bases, namespace):
    print("Called with", name)
    return MyMetaclass(name, bases, namespace)

class MyClass(metaclass=metaclass_callable):
    pass

class MyDerived(MyClass):
    pass

print(type(MyClass), type(MyDerived))

问题1

MyClass的元类:metaclass_callable还是MyMetaclass?文档中的第二条规则说,提供的可调用“直接用作元类”。但是,从

开始,说元类是MyMetaclass似乎更有意义
  • MyClassMyDerived的类型为MyMetaclass
  • metaclass_callable被调用一次,然后似乎无法恢复,
  • 派生类不以任何方式使用(据我所知)metaclass_callable(他们使用MyMetaclass)。

问题2

对于type的实例,您无法对callable执行任何操作吗?接受任意可调用的目的是什么?

3 个答案:

答案 0 :(得分:7)

关于你的第一个问题,元类应该是MyMetaclass(它是这样的):

In [7]: print(type(MyClass), type(MyDerived))
<class '__main__.MyMetaclass'> <class '__main__.MyMetaclass'>

原因是,如果元类不是python类型的实例,则通过将这些参数传递给它name, bases, ns, **kwds来调用methaclass(参见new_class),因为你要在该函数中返回真正的元类它获得了元类的正确类型。

关于第二个问题:

  

接受任意可调用的目的是什么?

没有特殊目的,它实际上是元类的本质,这是因为从类中创建实例总是通过调用它来调用元类{{1方法:

__call__

这意味着你可以传递任何可调用的元类作为你的元类。因此,例如,如果使用嵌套函数对其进行测试,结果仍将是相同的:

Metaclass.__call__()

有关更多信息,请参阅Python如何创建类:

它调用In [21]: def metaclass_callable(name, bases, namespace): def inner(): return MyMetaclass(name, bases, namespace) return inner() ....: In [22]: class MyClass(metaclass=metaclass_callable): pass ....: In [23]: print(type(MyClass), type(MyDerived)) <class '__main__.MyMetaclass'> <class '__main__.MyMetaclass'> 函数,它在其自身内部调用new_class,然后正如您在prepare_class python中看到的那样调用相应元类的prepare_class方法,旁边找到合适的元(使用__prepare__函数)并为类创建适当的命名空间。

所以这里所有的都是执行metacalss方法的层次结构:

  1. _calculate_meta 1
  2. __prepare__
  3. __call__
  4. __new__
  5. 这是源代码:

    __init__

    <子> 1.请注意,它会在 new_class 函数内部以及返回之前隐式调用。

答案 1 :(得分:2)

嗯,type当然是MyMetaClassmetaclass_callable最初被选中&#39;}作为自it's been specified in the metaclass kwarg以来的元类,因此,它将执行__call__(一个简单的函数调用)。

恰好调用它print,然后调用MyMetaClass.__call__(因为type.__call__ __call__已被覆盖MyMetaClass而调用MyMetaClass })。 There the assignment of cls.__class__ is mademetaclass_callable

  

MyMetaClass被调用一次然后似乎无法恢复

是的,它最初只是被调用,然后手动控制到metaclass_callable。我不知道任何保留该信息的类属性。

  

派生类不以任何方式使用(据我所知)metaclass

不,如果没有明确定义MyClass,则会使用the best match for the metaclasses of bases(此处为MyMetaClass)(生成2)。

对于问题__call__,通过使用相应覆盖answer == 10 // YES - It'll print "You are correct!" answer == 9 // No - Skip to the next If block answer == 11? // No - skip to the else and print "You are wrong! Try again!" 的类型实例,可以确保使用可调用的所有操作。至于为什么,如果您只是想在实际创建类时进行微小的更改,则可能不想进行全面的类创建。

答案 2 :(得分:2)

关于问题1,我认为课程cls的“元类”应理解为type(cls)。这种理解方式与以下示例中的Python错误消息兼容:

>>> class Meta1(type): pass
... 
>>> class Meta2(type): pass
... 
>>> def metafunc(name, bases, methods):
...     if methods.get('version') == 1:
...         return Meta1(name, bases, methods)
...     return Meta2(name, bases, methods)
... 
>>> class C1:
...     __metaclass__ = metafunc
...     version = 1
... 
>>> class C2:
...     __metaclass__ = metafunc
...     version = 2
... 
>>> type(C1)
<class '__main__.Meta1'>
>>> type(C2)
<class '__main__.Meta2'>
>>> class C3(C1,C2): pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
    metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

即,根据错误消息,类的元类是一个类,即使用于构造类的可调用类也可以是任何类。

关于第二个问题,实际上使用类型的子类作为元类,您可以像使用任何其他可调用类一样。特别是,它可能会产生一些不是它的实例:

>>> class Mockup(type):
...     def __new__(cls, name, bases, methods):
...         return Meta1(name, bases, methods)
... 
>>> class Foo:
...     __metaclass__ = Mockup
... 
>>> type(Foo)
<class '__main__.Meta1'>
>>> isinstance(Foo, Mockup)
False
>>> Foo.__metaclass__
<class '__main__.Mockup'>

至于为什么Python允许使用任何可调用的自由:前面的例子表明,callable是否是一个类型实际上是无关紧要的。

顺便说一下,这是一个有趣的例子:可以编写元类,它们本身具有与type不同的元类 - 让我们称之为元类。 metametaclass实现调用元类时发生的事情。通过这种方式,可以创建一个具有两个基类的类,其中元类是不是彼此的子类(与上面示例中的Python错误消息相比!)。实际上,只有结果类的元类是基类的元类的子类,并且这个元类是动态创建的:

>>> class MetaMeta(type):
...     def __call__(mcls, name, bases, methods):
...         metabases = set(type(X) for X in bases)
...         metabases.add(mcls)
...         if len(metabases) > 1:
...             mcls = type(''.join([X.__name__ for X in metabases]), tuple(metabases), {})
...         return mcls.__new__(mcls, name, bases, methods)
... 
>>> class Meta1(type):
...     __metaclass__ = MetaMeta
... 
>>> class Meta2(type):
...     __metaclass__ = MetaMeta
... 
>>> class C1:
...     __metaclass__ = Meta1
... 
>>> class C2:
...     __metaclass__ = Meta2
... 
>>> type(C1)
<class '__main__.Meta1'>
>>> type(C2)
<class '__main__.Meta2'>
>>> class C3(C1,C2): pass
... 
>>> type(C3)
<class '__main__.Meta1Meta2'>

没什么好玩的:前面的例子在Python 3中不起作用。如果我理解正确,Python 2会创建类并检查它的元类是否是其所有基类的子类,而Python 3 首先< / em>检查是否有一个基类,其元类是所有其他基类的元类的超类,只有然后创建新类。从我的角度来看,这是一个回归。但这将成为我即将发布的新问题的主题......

修改:新问题为here