【问题标题】:Why the metaclass definition changed the mro of classic class?为什么元类定义改变了经典类的mro?
【发布时间】:2017-10-02 13:53:23
【问题描述】:

根据Method Resolution Order

经典类使用简单的 MRO 方案:查找方法时, 使用简单的深度优先从左到右搜索基类 方案。

这可以在 Python 2.6 中验证

In [1]: import sys

In [2]: sys.version
Out[2]: '2.6.6 (r266:84292, Jul 23 2015, 15:22:56) \n[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 中得到不同的结果:

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??

【问题讨论】:

  • 我无法在 2.7.14 [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.37)] (Mac OSX)、2.7.13 (default, Jan 19 2017, 14:48:08) \n[GCC 6.3.0 20170118] (Debian 9.0) 或 2.7.12 (default, Nov 19 2016, 06:48:10) \n[GCC 5.4.0 20160609] (Ubuntu 16.04,看起来与版本完全相同) 上重现您的 2.7 行为你正在运行)。结果始终为D.f()。只有当我使用新式类时,他们才会报告C.f()
  • @birryree 抱歉我的误导性问题。我发现我的结果是由于之前对元类的定义。我已经修改了问题。

标签: python python-2.7 method-resolution-order


【解决方案1】:

元类仅适用于新型类,并通过在类主体中具有名为__metaclass__ 的字段来添加。无论您是否从object 继承,拥有__metaclass__ 字段都会使该类成为新型类:

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 将其应用于类:

class D:
  __metaclass__ = PrettyPrintClassRepr

【讨论】:

    猜你喜欢
    • 2018-09-10
    • 2013-11-20
    • 2016-05-20
    • 2019-10-03
    • 1970-01-01
    • 1970-01-01
    • 2011-01-04
    • 2021-12-04
    • 2022-11-16
    相关资源
    最近更新 更多