Recipe20.10.Using _ _new_ _ and _ _init_ _ Appropriately in Custom Metaclasses


Recipe 20.10. Using _ _new_ _ and _ _init_ _ Appropriately in Custom Metaclasses

Credit: Michele Simionato, Stephan Diehl, Alex Martelli

Problem

You are writing a custom metaclass, and you are not sure which tasks your metaclass should perform in its _ _new_ _ method, and which ones it should perform in its _ _init_ _ method instead.

Solution

Any preliminary processing that your custom metaclass performs on the name, bases, or dict of the class being built, can affect the way in which the class object gets built only if it occurs in the metaclass' _ _new_ _ method, before your code calls the metaclass' superclass' _ _new_ _. For example, that's the only time when you can usefully affect the new class' _ _slots_ _, if any:

class MetaEnsure_foo(type):     def _ _new_ _(mcl, cname, cbases, cdict):         # ensure instances of the new class can have a '_foo' attribute         if '_ _slots_ _' in cdict and '_foo' not in cdict['_ _slots_ _']:             cdict['_ _slots_ _'] = tuple(cdict['_ _slots_ _']) + ('_foo',)         return super(MetaEnsure_foo, mcl)._ _new_ _(mcl, cname, cbases, cdict)

Metaclass method _ _init_ _ is generally the most appropriate one for any changes that your custom metaclass makes to the class object after the class object is builtfor example, continuing the example code for metaclass MetaEnsure_foo:

    def _ _init_ _(cls, cname, cbases, cdict):         super(MetaEnsure_foo, cls)._ _init_ _(cls, cname, cbases, cdict)         cls._foo = 23

Discussion

The custom metaclass MetaEnsure_foo performs a definitely "toy" task presented strictly as an example: if the class object being built defines a _ _slots_ _ attribute (to save memory), MetaEnsure_foo ensures that the class object includes a slot _foo, so that instances of that class can have an attribute thus named. Further, the custom metaclass sets an attribute with name _foo and value 23 on each new class object. The point of the recipe isn't really this toy task, but rather, a clarification on how _ _new_ _ and _ _init_ _ methods of a custom metaclass are best coded, and which tasks are most appropriate for each.

Whenever you instantiate any class x (whether x is a custom metaclass or an ordinary class) with or without arguments (we can employ the usual Python notation *a, **k to mean arbitrary positional and named arguments), Python internally performs the equivalent of the following snippet of code:

    new_thing = X._ _new_ _(X, *a, **k)     if isinstance(new_thing, X):         X._ _init_ _(new_thing, *a, **k)

The new_thing thus built and initialized is the result of instantiating x. If x is a custom metaclass, in particular, this snippet occurs at the end of the execution of a class statement, and the arguments (all positional) are the name, bases, and dictionary of the new class that is being built.

So, your custom metaclass' _ _new_ _ method is the code that has dibsit executes first. That's the moment in which you can adjust the name, bases, and dictionary that you receive as arguments, to affect the way the new class object is built. Most characteristics of the class object, but not all, can also be changed later. An example of an attribute that you have to set before building the class object is _ _slots_ _. Once the class object is built, the slots, if any, are defined, and any further change to _ _slots_ _ has no effect.

The custom metaclass in this recipe carefully uses super to delegate work to its superclass, rather than carelessly calling type._ _new_ _ or type._ _init_ _ directly: the latter usage would be a subtle mistake, impeding the proper working of multiple inheritance among metaclasses. Further, this recipe is careful in naming the first parameters to both methods: cls to mean an ordinary class (the object that is the first argument to a custom metaclass' _ _init_ _), mcl to mean a metaclass (the object that is the first argument to a custom metaclass' _ _new_ _). The common usage of self should be reserved to mean normal instances, not classes nor metaclasses, and therefore it doesn't normally occur in the body of a custom metaclass. All of these names are a matter of mere convention, but using appropriate conventions promotes clarity, and this use of cls and mcl was blessed by Guido van Rossum himself, albeit only verbally.

The usage distinction between _ _new_ _ and _ _init_ _ that this recipe advocates for custom metaclasses is basically the same criterion that any class should always follow: use _ _new_ _ when you must, only for jobs that cannot be done later; use _ _init_ _ for all jobs that can be left until _ _init_ _ time. Following these conventions makes life easiest for anybody who must tweak your custom metaclass or make it work well in a multiple inheritance situation, and thus enhances the reusability of your code. _ _new_ _ should contain only the essence of your metaclass: stuff that anybody using your metaclass in any way at all must surely want (or else he wouldn't be using your metaclass!) because it's stuff that's not easy to tweak, modify, or override. _ _init_ _ is "softer", so most of what your metaclass is doing to the class objects you generate, should be there, exactly because it will be easier for reusers to tweak or avoid.

See Also

Library Reference and Python in a Nutshell docs on built-ins super and _ _slots_ _, and special methods _ _init_ _ and _ _new_ _.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net