Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

metaclass and __prepare__ ()

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!

like image 317
luoshao23 Avatar asked Oct 19 '17 10:10

luoshao23


People also ask

What is __ metaclass __ in Python?

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.

What is the concept of a metaclass?

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.

What is __ Init_subclass __ in Python?

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().

How do you create a metaclass in Python?

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.


1 Answers

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 as namespace = 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.

like image 168
jsbueno Avatar answered Nov 13 '22 06:11

jsbueno