| PEP: | 3115 |
|---|---|
| Title: | Metaclasses in Python 3000 |
| Author: | Talin <viridia at gmail.com> |
| Status: | Final |
| Type: | Standards Track |
| Created: | 07-Mar-2007 |
| Python-Version: | 3.0 |
| Post-History: | 11-March-2007, 14-March-2007 |
Contents
Abstract
This PEP proposes changing the syntax for declaring metaclasses, and alters the semantics for how classes with metaclasses are constructed.
Rationale
There are two rationales for this PEP, both of which are somewhat subtle.
The primary reason for changing the way metaclasses work, is that there are a number of interesting use cases that require the metaclass to get involved earlier in the class construction process than is currently possible. Currently, the metaclass mechanism is essentially a post-processing step. With the advent of class decorators, much of these post-processing chores can be taken over by the decorator mechanism.
In particular, there is an important body of use cases where it would be useful to preserve the order in which a class members are declared. Ordinary Python objects store their members in a dictionary, in which ordering is unimportant, and members are accessed strictly by name. However, Python is often used to interface with external systems in which the members are organized according to an implicit ordering. Examples include declaration of C structs; COM objects; Automatic translation of Python classes into IDL or database schemas, such as used in an ORM; and so on.
In such cases, it would be useful for a Python programmer to specify such ordering directly using the declaration order of class members. Currently, such orderings must be specified explicitly, using some other mechanism (see the ctypes module for an example.)
Unfortunately, the current method for declaring a metaclass does not allow for this, since the ordering information has already been lost by the time the metaclass comes into play. By allowing the metaclass to get involved in the class construction process earlier, the new system allows the ordering or other early artifacts of construction to be preserved and examined.
There proposed metaclass mechanism also supports a number of other interesting use cases beyond preserving the ordering of declarations. One use case is to insert symbols into the namespace of the class body which are only valid during class construction. An example of this might be "field constructors", small functions that are used in the creation of class members. Another interesting possibility is supporting forward references, i.e. references to Python symbols that are declared further down in the class body.
The other, weaker, rationale is purely cosmetic: The current method for specifying a metaclass is by assignment to the special variable __metaclass__, which is considered by some to be aesthetically less than ideal. Others disagree strongly with that opinion. This PEP will not address this issue, other than to note it, since aesthetic debates cannot be resolved via logical proofs.
Specification
In the new model, the syntax for specifying a metaclass is via a keyword argument in the list of base classes:
class Foo(base1, base2, metaclass=mymeta):
...
Additional keywords will also be allowed here, and will be passed to the metaclass, as in the following example:
class Foo(base1, base2, metaclass=mymeta, private=True):
...
Note that this PEP makes no attempt to define what these other keywords might be - that is up to metaclass implementors to determine.
More generally, the parameter list passed to a class definition will now support all of the features of a function call, meaning that you can now use *args and **kwargs-style arguments in the class base list:
class Foo(*bases, **kwds):
...
Example
Here's a simple example of a metaclass which creates a list of the names of all class members, in the order that they were declared:
# The custom dictionary
class member_table(dict):
def __init__(self):
self.member_names = []
def __setitem__(self, key, value):
# if the key is not already defined, add to the
# list of keys.
if key not in self:
self.member_names.append(key)
# Call superclass
dict.__setitem__(self, key, value)
# The metaclass
class OrderedClass(type):
# The prepare function
@classmethod
def __prepare__(metacls, name, bases): # No keywords in this case
return member_table()
# The metaclass invocation
def __new__(cls, name, bases, classdict):
# Note that we replace the classdict with a regular
# dict before passing it to the superclass, so that we
# don't continue to record member names after the class
# has been created.
result = type.__new__(cls, name, bases, dict(classdict))
result.member_names = classdict.member_names
return result
class MyClass(metaclass=OrderedClass):
# method1 goes in array element 0
def method1(self):
pass
# method2 goes in array element 1
def method2(self):
pass
Alternate Proposals
Josiah Carlson proposed using the name 'type' instead of 'metaclass', on the theory that what is really being specified is the type of the type. While this is technically correct, it is also confusing from the point of view of a programmer creating a new class. From the application programmer's point of view, the 'type' that they are interested in is the class that they are writing; the type of that type is the metaclass.
There were some objections in the discussion to the 'two-phase' creation process, where the metaclass is invoked twice, once to create the class dictionary and once to 'finish' the class. Some people felt that these two phases should be completely separate, in that there ought to be separate syntax for specifying the custom dict as for specifying the metaclass. However, in most cases, the two will be intimately tied together, and the metaclass will most likely have an intimate knowledge of the internal details of the class dict. Requiring the programmer to insure that the correct dict type and the correct metaclass type are used together creates an additional and unneeded burden on the programmer.
Another good suggestion was to simply use an ordered dict for all classes, and skip the whole 'custom dict' mechanism. This was based on the observation that most use cases for a custom dict were for the purposes of preserving order information. However, this idea has several drawbacks, first because it means that an ordered dict implementation would have to be added to the set of built-in types in Python, and second because it would impose a slight speed (and complexity) penalty on all class declarations. Later, several people came up with ideas for use cases for custom dictionaries other than preserving field orderings, so this idea was dropped.
Backwards Compatibility
It would be possible to leave the existing __metaclass__ syntax in place. Alternatively, it would not be too difficult to modify the syntax rules of the Py3K translation tool to convert from the old to the new syntax.
Copyright
This document has been placed in the public domain.