关于python:为什么元类定义改变了经典类的mro?

Why the metaclass definition changed the mro of classic class?

根据方法分辨率顺序:

Classic classes used a simple MRO scheme: when looking up a method,
base classes were searched using a simple depth-first left-to-right
scheme.

这可以在python 2.6中验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [1]: import sys

In [2]: sys.version
Out[2]: '2.6.6 (r266:84292, Jul 23 2015, 15:22:56)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-11)]'


In [3]:
class D:
    def f(self): return 'D'
class B(D): pass
class C(D):
    def f(self): return 'C'
class A(B, C): pass
   ...:

In [4]: A().f()
Out[4]: 'D'

但是,如果我定义了一个元类,那么在python 2.7.12中得到了不同的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
Type"copyright","credits" or"license" for more information.

IPython 5.4.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python'
s own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: class D:       # Note: Old-style
   ...:     def f(self): return"D.f()"
   ...: class B(D): pass
   ...: class C(D):
   ...:     def f(self): return"C.f()"
   ...: class A(B, C): pass
   ...:

In [2]: A().f()
Out[2]: 'D.f()'    # It works as expected.

In [3]: class __metaclass__(type):
   ...:    "All classes are metamagically modified to be nicely printed"
   ...:     __repr__ = lambda cls: cls.__name__
   ...:    

In [4]: class D:       # Note: Old-style
   ...:     def f(self): return"D.f()"
   ...: class B(D): pass
   ...: class C(D):
   ...:     def f(self): return"C.f()"
   ...: class A(B, C): pass
   ...:

In [5]: A().f()
Out[5]: 'C.f()'   # WTF??


元类只适用于新样式的类,并且通过在类体中有一个名为__metaclass__的字段来添加。拥有一个__metaclass__字段会使类成为一个新的样式类,无论您是否从object继承:

1
2
3
4
5
6
7
8
9
10
class J:
  pass
class K(object):
  pass
class L:
  __metaclass__ = type

type(J) # <type 'classobj'>
type(K) # <type 'type'>
type(L) # <type 'type'>

因此,在您的示例中发生的事情似乎是,由于词汇/静态作用域规则,当第二次执行类D的主体时,名为__metaclass__的元类处于作用域中。由于在执行主体时,python在作用域中看到了一个__metaclass__,所以它将其用作D的元类,并将其转换为一个新的样式类,从而改变了方法的解析顺序。

您可能不应该命名一个元类__metaclass__。将其命名为类似于PrettyPrintClassRepr的描述性名称,并通过在类体中使用__metaclass__ = PrettyPrintClassRepr将其应用到类中:

1
2
class D:
  __metaclass__ = PrettyPrintClassRepr