Explore the ancestor of Python class - metaclass

Everything in Python is an object

Python is an object-oriented language, so numbers, strings, lists, collections, dictionaries, functions, classes, etc. in Python are objects.

Use type() to view various object types in Python

In [11]: # number

In [12]: type(10)
Out[12]: int

In [13]: type(3.1415926)
Out[13]: float

In [14]: # character string

In [15]: type('a')
Out[15]: str

In [16]: type("abc")
Out[16]: str

In [17]: # list

In [18]: type(list)
Out[18]: type

In [19]: type([])
Out[19]: list

In [20]: # aggregate

In [21]: type(set)
Out[21]: type

In [22]: my_set = {1, 2, 3}

In [23]: type(my_set)
Out[23]: set

In [24]: # Dictionaries

In [25]: type(dict)
Out[25]: type

In [26]: my_dict = {'name': 'hui'}

In [27]: type(my_dict)
Out[27]: dict

In [28]: # function

In [29]: def func():
    ...:     pass
    ...:

In [30]: type(func)
Out[30]: function

In [31]: # class

In [32]: class Foo(object):
    ...:     pass
    ...:

In [33]: type(Foo)
Out[33]: type

In [34]: f = Foo()

In [35]: type(f)
Out[35]: __main__.Foo

In [36]: # type

In [37]: type(type)
Out[37]: type

It can be seen that

  • The number 1 is an object of type int
  • The string abc is an object of type str
  • List, set and dictionary are objects of type, and only the created objects belong to list, set and dict types respectively
  • The function func is an object of type function
  • The object f created by the custom class Foo is of type Foo, and the class itself Foo is of type object.
  • Even type itself is an object of type

1. A class is also an object

A class is a collection of objects with equal functions and the same properties

In most programming languages, a class is a set of code snippets that describe how to generate an object. This still holds in Python:

In [1]: class ObjectCreator(object):
   ...:     pass
   ...:

In [2]: my_object = ObjectCreator()

In [3]: print(my_object)
<__main__.ObjectCreator object at 0x0000021257B5A248>

However, there are far more classes in Python. Class is also an object. Yes, yes, it's the object. As long as you use the keyword class, the Python interpreter will create an object when executing.

The following code snippet:

>>> class ObjectCreator(object):
...       pass
...

An object named ObjectCreator will be created in memory. This object (class object ObjectCreator) has the ability to create objects (instance objects). However, its essence is still an object, so you can do the following operations on it:

  1. You can assign it to a variable
  2. You can copy it
  3. You can add attributes to it
  4. You can pass it as a function parameter

Examples are as follows:

In [39]: class ObjectCreator(object):
    ...:     pass
    ...:

In [40]: print(ObjectCreator)
<class '__main__.ObjectCreator'>

In [41]:# Pass as parameter

In [41]: def out(obj):
    ...:     print(obj)
    ...:

In [42]: out(ObjectCreator)
<class '__main__.ObjectCreator'>

In [43]: # hasattr determines whether a class has some attribute

In [44]: hasattr(ObjectCreator, 'name')
Out[44]: False

In [45]: # New class attribute

In [46]: ObjectCreator.name = 'hui'

In [47]: hasattr(ObjectCreator, 'name')
Out[47]: True

In [48]: ObjectCreator.name
Out[48]: 'hui'

In [49]: # Assign a class to a variable

In [50]: obj = ObjectCreator

In [51]: obj()
Out[51]: <__main__.ObjectCreator at 0x212596a7248>

In [52]:

2. Create classes dynamically

Because classes are also objects, you can create them dynamically at run time, just like any other object. First, you can create a class in the function by using the class keyword.

def cls_factory(cls_name):
    """
    Create class factory
    :param: cls_name Name of the created class
    """
    if cls_name == 'Foo':
        class Foo():
            pass
        return Foo  # The returned is a class, not an instance of a class

    elif cls_name == 'Bar':
        class Bar():
            pass
        return Bar

IPython test

MyClass = cls_factory('Foo')

In [60]: MyClass
Out[60]: __main__.cls_factory.<locals>.Foo # The function returns a class, not an instance of the class

In [61]: MyClass()
Out[61]: <__main__.cls_factory.<locals>.Foo at 0x21258b1a9c8>

But that's not dynamic enough, because you still need to write the entire class yourself. Since classes are also objects, they must be generated by something.

When you use the class keyword, the Python interpreter automatically creates this object. But like most things in Python, python still gives you a way to handle it manually.

3. Create a class using type

type also has a completely different function, creating classes dynamically.

type can take a description of a class as a parameter and then return a class. (you know, it's silly to have two completely different usages of the same function according to the passed in parameters, but this is to maintain backward compatibility in Python)

type works like this:

Type (class name, tuple composed of parent class name (it can be empty for inheritance), dictionary containing attributes (name and value))

For example, the following code:

In [63]: class Test:
    ...:     pass
    ...:

In [64]: Test()
Out[64]: <__main__.Test at 0x21258b34048>

In [65]:

You can create it manually like this:

In [69]:# Defining classes using type

In [69]: Test2 = type('Test2', (), {})

In [70]: Test2()
Out[70]: <__main__.Test2 at 0x21259665808>

We use Test2 as the class name, and we can also use it as a variable as a reference to the class. Classes and variables are different. There is no reason to complicate things here. That is, the first argument in the type function can also be called other names. This name represents the name of the class

In [71]: UserCls = type('User', (), {})

In [72]: print(UserCls)
<class '__main__.User'>

In [73]:

Use help to test these two classes

In [74]: # View the Test class with help

In [75]: help(Test)
Help on class Test in module __main__:

class Test(builtins.object)
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)


In [76]: # View Test2 class with help

In [77]: help(Test2)
Help on class Test2 in module __main__:

class Test2(builtins.object)
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)


In [78]:

4. Use type to create a class with attributes

type accepts a dictionary to define attributes for a class, so

Parent = type('Parent', (), {'name': 'hui'})

Can be translated as:

class Parent(object):
	name = 'hui'

And you can use Parent as an ordinary class:

In [79]: Parent = type('Parent', (), {'name': 'hui'})

In [80]: print(Parent)
<class '__main__.Parent'>

In [81]: Parent.name
Out[81]: 'hui'

In [82]: p = Parent()

In [83]: p.name
Out[83]: 'hui'

Of course, you can inherit this class. The code is as follows:

class Child1(Parent):
    name = 'jack'
    sex =  'male'
    
class Child2(Parent):
    name = 'mary'
    sex = 'female'

It can be written as:

 Child1 = type('Child1', (Parent, ), {'name': 'jack', 'sex': 'male'})

In [85]: Child2 = type('Child2', (Parent, ), {'name': 'mary', 'sex': 'female'})

In [87]: Child1.name, Child1.sex
Out[87]: ('jack', 'male')

In [88]: Child2.name, Child2.sex
Out[88]: ('mary', 'female')

be careful:

  • The second parameter of type is the name of the parent class in the tuple, not the string
  • The added attribute is a class attribute, not an instance attribute

5. Use type to create a class with methods

Eventually you will want to add methods to your class. Just define a function with the appropriate signature and assign it as an attribute.

Add instance method

In [89]: Parent = type('Parent', (), {'name': 'hui'})

In [90]: # Define function

In [91]: def get_name(self):
    ...:     return self.name
    ...:

In [92]: Child3 = type('Child3', (Parent, ), {'name': 'blob', 'get_name': get_name})

In [93]: c3 = Child3()

In [94]: c3.name
Out[94]: 'blob'

In [95]: c3.get_name()
Out[95]: 'blob'

Add static method

In [96]: Parent = type('Parent', (), {'name': 'hui'})

In [97]: # Define static methods
    
In [98]: @staticmethod
    ...: def test_static():
    ...:     print('static method called...')
    ...:

In [100]: Child4 = type('Child4', (Parent, ), {'name': 'zhangsan', 'test_static': test_static})

In [101]: c4 = Child4()

In [102]: c4.test_static()
static method called...

In [103]: Child4.test_static()
static method called...

Add class method

In [105]: Parent = type('Parent', (), {'name': 'hui'})

In [106]: # Define class methods

In [107]: @classmethod
     ...: def test_class(cls):
     ...:     print(cls.name)
     ...:

In [108]: Child5 = type('Child5', (Parent, ), {'name': 'lisi', 'test_class': test_class})

In [109]: c5 = Child5()

In [110]: c5.test_class()
lisi

In [111]: Child5.test_class()
lisi

You can see that in Python, classes are also objects, and you can create classes dynamically. This is what Python does behind the scenes when you use the keyword class, which is implemented through metaclasses.

A more complete way to create classes using type:

class Animal(object):
    
    def eat(self):
        print('Eat something')


def dog_eat(self):
    print('I like eating bones')

def cat_eat(self):
    print('I like fish')


Dog = type('Dog', (Animal, ), {'tyep': 'Mammals', 'eat': dog_eat})

Cat = type('Cat', (Animal, ), {'tyep': 'Mammals', 'eat': cat_eat})

# ipython test
In [125]: animal = Animal()

In [126]: dog = Dog()

In [127]: cat = Cat()

In [128]: animal.eat()
Eat something

In [129]: dog.eat()
I like eating bones

In [130]: cat.eat()
I like fish

6. What is metaclass (finally to the topic)

Metaclasses are the things used to create classes. You create a class to create an instance object of the class, don't you? But we have learned that classes in Python are also objects.

Metaclasses are used to create these classes (objects). Metaclasses are classes of classes. You can understand them as follows:

MyClass = MetaClass() # Use metaclasses to create an object called a class
my_object = MyClass() # Use class to create instance objects

You've seen that type allows you to do something like this:

MyClass = type('MyClass', (), {})

This is because the function type is actually a metaclass. Type is the metaclass Python uses to create all classes behind it. Now you want to know why type is all lowercase instead of type? Well, I guess this is consistent with str, which is the class used to create string objects and int is the class used to create integer objects. Type is the class that creates the class object. You can pass the inspection__ class__ Property to see this. So everything in Python is an object

Now, for any one__ class__ Yes__ class__ What are attributes?

In [136]: a = 10

In [137]: b = 'acb'

In [138]: li = [1, 2, 3]

In [139]: a.__class__.__class__
Out[139]: type

In [140]: b.__class__.__class__
Out[140]: type

In [141]: li.__class__.__class__
Out[141]: type

In [142]: li.__class__.__class__.__class__
Out[142]: type

Therefore, metaclasses are the things that create objects like classes. type is Python's built-in metaclass. Of course, you can also create your own metaclass.

7. __metaclass__ attribute

You can add to a class when you define it__ metaclass__ Properties.

class Foo(object):
    __metaclass__ = something...
    ...ellipsis...

If you do, python will use metaclasses to create class Foo. Be careful, there are some skills in it. You first write class Foo(object), but class Foo has not been created in memory. Python looks in the class definition__ metaclass__ Property. If found, python will use it to create class Foo. If not found, python will use the built-in type to create this class.

class Foo(Bar):
    pass

Python does the following:

  1. In Foo__ metaclass__ This property? If so, Python will pass__ metaclass__ Create a class (object) named Foo
  2. If Python doesn't find it__ metaclass__, It will continue to look in the Bar (parent class)__ metaclass__ Property and try to do the same as before.
  3. If Python cannot find any parent class__ metaclass__, It will look for it in the module level__ metaclass__, And try to do the same.
  4. If you still can't find it__ metaclass__ , Python creates this class object with the built-in type.

The problem now is that you can__ metaclass__ What code is placed in the?

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

8. User defined metaclass

The main purpose of metaclasses is to automatically change classes when they are created.

Imagine a silly example where you decide that the properties of all classes in your module should be capitalized. There are several ways to do this, but one is through setting at the module level__ metaclass__. Using this method, all classes in this module will be created through this metaclass. We just need to tell the metaclass to change all attributes to uppercase.

Fortunately__ metaclass__ In fact, it can be called arbitrarily, and it does not need to be a formal class. So let's start with a simple function as an example.

In python2

# -*- coding:utf-8 -*-
def upper_attr(class_name, class_parents, class_attr):

    # class_name saves the class name Foo
    # class_parents will save the parent object of the class
    # class_attr will store all class attributes in a dictionary

    # Traverse the attribute dictionary__ Property names that begin with are capitalized
    new_attr = {}
    for name, value in class_attr.items():
        if not name.startswith("__"):
            new_attr[name.upper()] = value

    # Call type to create a class
    return type(class_name, class_parents, new_attr)

class Foo(object):
    __metaclass__ = upper_attr # Set the metaclass of Foo class to upper_attr
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Flase
print(hasattr(Foo, 'BAR'))
# True

f = Foo()
print(f.BAR)

In Python 3

# -*- coding:utf-8 -*-
def upper_attr(class_name, class_parents, class_attr):

    #Traverse the attribute dictionary__ Property names that begin with are capitalized
    new_attr = {}
    for name,value in class_attr.items():
        if not name.startswith("__"):
            new_attr[name.upper()] = value

    #Call type to create a class
    return type(class_name, class_parents, new_attr)

# Use metaclass in class inheritance ()
class Foo(object, metaclass=upper_attr):
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Flase
print(hasattr(Foo, 'BAR'))
# True

f = Foo()
print(f.BAR)

Do it again, this time with a real class as the metaclass.

class UpperAttrMetaClass(type):
    
    def __new__(cls, class_name, class_parents, class_attr):
        # Traverse the attribute dictionary__ Property names that begin with are capitalized
        new_attr = {}
        for name, value in class_attr.items():
            if not name.startswith("__"):
                new_attr[name.upper()] = value

        # Method 1: create class objects through 'type'
        return type(class_name, class_parents, new_attr)

        # Method 2: reuse type__ new__ method
        # This is basic OOP programming. There is no magic
        # return type.__new__(cls, class_name, class_parents, new_attr)

        
# Usage of Python 3
class Foo(object, metaclass=UpperAttrMetaClass):
    bar = 'bip'

# Usage of python2
class Foo(object):
	__metaclass__ = UpperAttrMetaClass
    bar = 'bip'


print(hasattr(Foo, 'bar'))
# Output: False
print(hasattr(Foo, 'BAR'))
# Output: True

f = Foo()
print(f.BAR)
# Output: 'bip'
__new__ Yes__init__Special methods called before
__new__Is the method used to create an object and return it
 and__init__It is only used to initialize the passed in parameters to the object
 Here, the created object is a class. We want to customize it, so we rewrite it here__new__

That's it. There's really nothing else to say about metaclasses. But as far as metaclasses are concerned, they are actually very simple:

  1. Creation of interception class
  2. Modify class
  3. Returns the modified class

Why use metaclasses?

Now back to our big topic, why do you use such an error prone and obscure feature?

Well, generally speaking, you don't need it at all:

"Metaclasses are deep magic, and 99% of users should not worry about it at all. If you want to find out whether you need to use metaclasses, you don't need them. Those who actually use metaclasses know exactly what they need to do and don't need to explain why they use metaclasses." - Tim Peters, leader of Python

source code

The source code has been uploaded to Gitee Python knowledge: a treasure house of Python knowledge , welcome to visit.

Posted by aebstract on Mon, 06 Dec 2021 19:07:17 -0800