I am teaching myself about the __prepare__
function. And I see this snippet at PEP3115
# 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
My question is at this line:
result.member_names = classdict.member_names
How could the variable classdict
get a attribute from the member_table
class? I see the __prepare__
function returns an instance of member_table, but how is the link between member_table()
and classdict.member_names
generated?
Many thanks to all of you!
A metaclass in Python is a class of a class that defines how a class behaves. A class is itself an instance of a metaclass. A class in Python defines how the instance of the class will behave. In order to understand metaclasses well, one needs to have prior experience working with Python classes.
In object-oriented programming, a metaclass is a class whose instances are classes. Just as an ordinary class defines the behavior of certain objects, a metaclass defines the behavior of certain classes and their instances. Not all object-oriented programming languages support metaclasses.
The __init_subclass__ class method is called when the class itself is being constructed. It gets passed the cls and can make modifications to it. Here's the pattern I used: class AsyncInject: def __init_subclass__(cls, **kwargs): super().
To create your own metaclass in Python you really just want to subclass type . A metaclass is most commonly used as a class-factory. When you create an object by calling the class, Python creates a new class (when it executes the 'class' statement) by calling the metaclass.
That is pretty straightforward, as it is exactly what prepare does.
3.3.3.3. Preparing the class namespace Once the appropriate metaclass has been identified, then the class namespace is prepared. If the metaclass has a
__prepare__
attribute, it is called asnamespace = metaclass.__prepare__(name, bases, **kwds)
(where the additional keyword arguments, if any, come from the class definition).If the metaclass has no
__prepare__
attribute, then the class namespace is initialised as an empty ordered mapping.
https://docs.python.org/3/reference/datamodel.html#preparing-the-class-namespace
Which means, the classdict
attribute that is passed into the metaclass __new__
and __init__
methods is exactly the same object that is returned by __prepare__
.
That object should be a mapping instance, that is, an object that behaves like a dict and have at least the __setitem__
method. This __setitem__
method is called by Python for all variables set inside the the declared class body itself.
That is, for an ordinary class, with no custom metaclass, the variables are recorded in a dictionary (an ordered dictionary, as of Python 3.6).
That happens as Python runs each statement inside the class body. This is the same object that is returned should one call locals()
inside the class body:
In [21]: class M(type):
...: @classmethod
...: def __prepare__(cls, *args):
...: class CustomDict(dict):
...: __repr__ = lambda self: "I am a custom dict: " + str(id(self))
...: namespace = CustomDict()
...: print("From __prepare__", namespace)
...: return namespace
...:
...: def __new__(metacls, name, bases, namespace):
...: print("From __new__:", namespace)
...: return super().__new__(metacls, name, bases, namespace)
...:
...:
In [22]: class Test(metaclass=M):
...: def __init__(self):
...: ...
...: print("From class body:", locals(), locals()["__init__"])
...:
...:
From __prepare__ I am a custom dict: 140560887720440
From class body: I am a custom dict: 140560887720440 <function Test.__init__ at 0x7fd6e1bd7158>
From __new__: I am a custom dict: 140560887720440
The main use case when this feature was first designed probably was exactly the possibility of making the order of declarations inside a class body meaningful. That is, a __prepare__
method could just return a collections.OrderedDict
instance, and __new__
or __init__
would act upon that order. As of Python 3.6, ordering of class attributes is by default - and the __prepare__
feature remains so advanced one really has to think up of uses for it.
Edit: Usually __prepare__
is implemented as a class method as it is called before the metaclass is instantiated.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With