Eastsun Eastsun - 2 years ago 137
Python Question

Why the metaclass definition changed the mro of classic class?

According to Method Resolution Order:


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.


This can be verified in 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'


However, I got a different result in Python 2.7.12 if I have defined a metaclass:

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

Answer Source

Metaclasses only apply to new-style classes, and are added by having a field named __metaclass__ in the class body. Having a __metaclass__ field causes the class to be a new-style class whether you inherit from object or not:

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

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

So what's happening in your example appears to be that due to lexical/static scoping rules, your metaclass named __metaclass__ is in scope when the body of class D is executed the second time. Since Python sees a __metaclass__ in scope when the body is executed, it uses that as the metaclass for D and turns it into a new-style class, which changes the method resolution order.


You probably shouldn't be naming a metaclass __metaclass__. Name it something descriptive like PrettyPrintClassRepr and apply it to classes when you want it by using __metaclass__ = PrettyPrintClassRepr in the class body:

class D:
  __metaclass__ = PrettyPrintClassRepr
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download