Custom metaclas...LOGIN

Custom metaclass

So far, we already know what metaclasses are. So, from beginning to end, we still don’t know what the use of metaclasses is. Just learned about metaclasses. Before understanding its use, let's first understand how to customize metaclasses. Because only by understanding how to customize it can you better understand its function.

First let’s understand the __metaclass__ attribute

metaclass, literally translated as metaclass, the simple explanation is:

When we define the class , you can create an instance based on this class, so: define the class first, and then create the instance.

But what if we want to create a class? Then you must create a class based on metaclass, so: define metaclass first, and then create the class.

The connection is: first define metaclass, then you can create the class, and finally create the instance.

So, metaclass allows you to create classes or modify classes. In other words, you can think of classes as "instances" created by metaclass.

class MyObject(object):
    __metaclass__ = something…
[…]

If written like this, Python will use metaclasses to create the class MyObject. When you write class MyObject(object), the class object MyObject has not been created in memory yet. Python will look for the __metaclass__ attribute in the class definition. If it is found, Python will use it to create the class MyObject. If it is not found, it will use the built-in type function to create the class. If you still don’t understand it, take a look at the flow chart below:

c7dd72cd3c802993425a3b6516a97e2.png

Another example:

class Foo(Bar):
    pass

What is its judgment process?

First determine whether there is a __metaclass__ attribute in Foo? If there is, Python will create a class object named Foo in memory through __metaclass__ (note that this is a class object). If Python does not find __metaclass__, it will continue to look for the __metaclass__ attribute in Bar (the parent class) and try to do the same operation as before. If Python cannot find __metaclass__ in any parent class, it looks for __metaclass__ in the module hierarchy and tries to do the same. If __metaclass__ is still not found, Python will use the built-in type to create the class object.

In fact, __metaclass__ defines the behavior of class. Similar to how class defines the behavior of an instance, metaclass defines the behavior of a class. It can be said that class is an instance of metaclass.

Now, we have a basic understanding of the __metaclass__ attribute, but we haven’t talked about how to use this attribute, or what can be put in this attribute?

The answer is: you can create a class. So what can be used to create a class? type, or anything that uses type or subclasses type.

The main purpose of metaclasses is to automatically change classes when they are created. Typically, you would do something like this for an API, where you want to create classes that fit the current context. Imagine a silly example where you decide that all class attributes in your module should be in uppercase. There are several ways to do this, but one of them is by setting __metaclass__ at the module level. Using this method, all classes in this module will be created through this metaclass. We only need to tell the metaclass to change all attributes to uppercase and everything will be fine.

Fortunately, __metaclass__ can actually be called arbitrary, it does not need to be a formal class. So, let's start with a simple function as an example.

# 元类会自动将你通常传给‘type’的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    '''返回一个类对象,将属性都转为大写形式'''
    #  选择所有不以'__'开头的属性
    attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
# 将它们转为大写形式
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
 
# 通过'type'来做类对象的创建
return type(future_class_name, future_class_parents, uppercase_attr)
 
__metaclass__ = upper_attr  
#  这会作用到这个模块中的所有类
 
class Foo(object):
    # 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
    bar = 'bip'
print hasattr(Foo, 'bar')
# 输出: False
print hasattr(Foo, 'BAR')
# 输出:True
 
f = Foo()
print f.BAR
# 输出:'bip'
用 class 当做元类的做法:
# 请记住,'type'实际上是一个类,就像'str'和'int'一样
# 所以,你可以从type继承
class UpperAttrMetaClass(type):
    # __new__ 是在__init__之前被调用的特殊方法
    # __new__是用来创建对象并返回之的方法
    # 而__init__只是用来将传入的参数初始化给对象
    # 你很少用到__new__,除非你希望能够控制对象的创建
    # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
    # 如果你希望的话,你也可以在__init__中做些事情
    # 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return type(future_class_name, future_class_parents, uppercase_attr)

However, this method is actually not OOP. We called type directly, and we did not override the __new__ method of the parent class. Now let's do it like this:

class UpperAttrMetaclass(type):
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
 
        # 复用type.__new__方法
        # 这就是基本的OOP编程,没什么魔法
        return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)

You may have noticed that there is an extra parameter upperattr_metaclass, which is nothing special. The first argument to a class method always represents the current instance, just like the self argument in an ordinary class method. Of course, for the sake of clarity, I've made the names here longer. But just like self, all parameters have their traditional names. So in real production code a metaclass would look like this:

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')
        uppercase_attr  = dict((name.upper(), value) for name, value in attrs)
        return type.__new__(cls, name, bases, uppercase_attr)

We can also make it a little clearer if we use the super method, which eases inheritance (yes, you You can have metaclasses, inherit from metaclasses, inherit from type)

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)

Usually we use metaclasses to do some obscure things, rely on introspection, control inheritance, etc. Indeed, it is particularly useful to use metaclasses to do some "dark magic" and thus create some complex things. But as far as the metaclasses themselves are concerned, they are actually very simple:

Creation of the interception class

Modification of the class

Return to the modified class

Next Section
submitReset Code
ChapterCourseware