Online Book Reader

Home Category

Learning Python - Mark Lutz [570]

By Root 1498 0
at the end of the declaring class statement, and it returns the expected new class object. The function is simply catching the call that the type object’s __call__ normally intercepts by default:

making class

In MetaFunc:

...Spam

...(,)

...{'__module__': '__main__', 'data': 1, 'meth': }

making instance

data: 1

Overloading class creation calls with metaclasses

Since they participate in normal OOP mechanics, it’s also possible for metaclasses to catch the creation call at the end of a class statement directly, by redefining the type object’s __call__. The required protocol is a bit involved, though:

# __call__ can be redefined, metas can have metas

class SuperMeta(type):

def __call__(meta, classname, supers, classdict):

print('In SuperMeta.call: ', classname, supers, classdict, sep='\n...')

return type.__call__(meta, classname, supers, classdict)

class SubMeta(type, metaclass=SuperMeta):

def __new__(meta, classname, supers, classdict):

print('In SubMeta.new: ', classname, supers, classdict, sep='\n...')

return type.__new__(meta, classname, supers, classdict)

def __init__(Class, classname, supers, classdict):

print('In SubMeta init:', classname, supers, classdict, sep='\n...')

print('...init class object:', list(Class.__dict__.keys()))

class Eggs:

pass

print('making class')

class Spam(Eggs, metaclass=SubMeta):

data = 1

def meth(self, arg):

pass

print('making instance')

X = Spam()

print('data:', X.data)

When this code is run, all three redefined methods run in turn. This is essentially what the type object does by default:

making class

In SuperMeta.call:

...Spam

...(,)

...{'__module__': '__main__', 'data': 1, 'meth': }

In SubMeta.new:

...Spam

...(,)

...{'__module__': '__main__', 'data': 1, 'meth': }

In SubMeta init:

...Spam

...(,)

...{'__module__': '__main__', 'data': 1, 'meth': }

...init class object: ['__module__', 'data', 'meth', '__doc__']

making instance

data: 1

Overloading class creation calls with normal classes

The preceding example is complicated by the fact that metaclasses are used to create class objects, but don’t generate instances of themselves. Because of this, with metaclasses name lookup rules are somewhat different than what we are accustomed to. The __call__ method, for example, is looked up in the class of an object; for metaclasses, this means the metaclass of a metaclass.

To use normal inheritance-based name lookup, we can achieve the same effect with normal classes and instances. The output of the following is the same as the preceding version, but note that __new__ and __init__ must have different names here, or else they will run when the SubMeta instance is created, not when it is later called as a metaclass:

class SuperMeta:

def __call__(self, classname, supers, classdict):

print('In SuperMeta.call: ', classname, supers, classdict, sep='\n...')

Class = self.__New__(classname, supers, classdict)

self.__Init__(Class, classname, supers, classdict)

return Class

class SubMeta(SuperMeta):

def __New__(self, classname, supers, classdict):

print('In SubMeta.new: ', classname, supers, classdict, sep='\n...')

return type(classname, supers, classdict)

def __Init__(self, Class, classname, supers, classdict):

print('In SubMeta init:', classname, supers, classdict, sep='\n...')

print('...init class object:', list(Class.__dict__.keys()))

class Eggs:

pass

print('making class')

class Spam(Eggs, metaclass=SubMeta()): # Meta is normal class instance

data = 1 # Called at end of statement

def meth(self, arg):

pass

print('making instance')

X = Spam()

print('data:', X.data)

Although these alternative forms work, most metaclasses get their work done by redefining the type superclass’s __new__ and __init__; in practice, this is usually as much control as is required, and it’s often simpler than other schemes.

Return Main Page Previous Page Next Page

®Online Book Reader