Почему определение метакласса изменило классическую классу?

Согласно Порядку разрешения метода:

Классические классы использовали простую схему 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??

1 ответ

Решение

Метаклассы применяются только к классам нового стиля и добавляются с помощью поля с именем __metaclass__ в теле класса. Иметь __metaclass__ поле заставляет класс быть классом нового стиля, наследуете ли вы от object или нет:

class J:
  pass
class K(object):
  pass
class L:
  __metaclass__ = type

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

Так что в вашем примере происходит то, что из-за лексических / статических правил видимости ваш метакласс называется __metaclass__ находится в области, когда тело класса D выполняется во второй раз. Так как Python видит __metaclass__ в области, когда тело выполняется, он использует его в качестве метакласса для D и превращает его в класс нового стиля, который меняет порядок разрешения метода.


Вы, вероятно, не должны называть метакласс __metaclass__, Назовите это как-то описательное PrettyPrintClassRepr и применить его к классам, когда вы хотите с помощью __metaclass__ = PrettyPrintClassRepr в теле класса:

class D:
  __metaclass__ = PrettyPrintClassRepr
Другие вопросы по тегам