Introduction of metaclasses in python

Keywords: Python Attribute Programming

Classes are also objects

In most programming languages, a class is a set of code snippets that describe how to generate an object, which is also true in python.

class ObjectCreator:
    pass
    
my_object = ObjectCreator()
print(my_object)
"""
//Output results:
<__main__.ObjectCreator object at 0x037DACD0>
"""

But python's classes are more than that. Classes are also objects.

class ObjectCreator:
    pass

The above code segment will create an object in memory called ObjectCreator. This object (class object creator) has the ability to create objects (instance objects), but it is still essentially an object, so you can do the following for it:

  • Copy a variable to it
  • Copy it
  • Add attributes to it
  • Pass it as a function parameter
    Sample code:
class ObjectCreator:
    pass
# Assign it to a variable
a = ObjectCreator
print(a) # <class '__main__.ObjectCreator'>
# Transfer as a function parameter
def echo(o):
    print(o)
    
echo(ObjectCreator) # <class '__main__.ObjectCreator'>

Creating classes dynamically

Because classes are also objects, they can be created dynamically at runtime using class keywords.

def choose_class(name):
    if name == 'foo':
        class Foo(object):
            pass
        return Foo     # Returns classes, not instances of classes
    else:
        class Bar(object):
            pass
        return Bar
MyClass = choose_class("foo")
print(MyClass) # Print class object
# Output results
<class '__main__.choose_class.<locals>.Foo'>

print(MyClass()) # Print instance objects
# Output results
<__main__.choose_class.<locals>.Foo object at 0x0368CFD0>

Create classes using type

We know what the type of this object is through type(), and it has a completely different function, creating classes dynamically.
type can accept a description of a class as a parameter, and then return a class.
Grammar:
Type (class name, tuple of parent class name (null for inheritance), dictionary containing attributes)

MyClass = type("MyClass",(),{})
print(MyClass)
# Output results:
<class '__main__.MyClass'>

Using type to create classes with attributes

type accepts a dictionary to define attributes for the class, as follows:

Foo = type("Foo",(),{'bar':True})

Equivalent to

class Foo:
    bar = True

Create inherited subclasses using type

Next to the above code, we have created a Foo class, and now we will create a subclass of it.

FooChild = type("FooChild",(Foo,),{})
print(FooChild.bar) # # The bar attribute is inherited from Foo
# Output results:
True

Be careful:

  • The second parameter of type is the name of the parent class in the tuple, not the string.
  • The added attributes are class attributes, not instance attributes.

Create classes with methods using type

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

Adding Instance Method

def test_f(self):
    print("Instance method added")
Foo = type("Foo",(),{"test_f":test_f})
f = Foo()
f.test_f()
# Output results:
//Instance method added

Adding static methods

@staticmethod
def test_static():
    print("Additional static methods")
Foo = type("Foo",(),{"test_static":test_static})
Foo.test_static()
Foo.test_static()
# Output results:
//Additional static methods

Adding class methods

@classmethod
def test_class(cls):
    print("Additional class methods")
Foo = type("Foo",(),{"test_class":test_class})
Foo.test_class()
# Output results:
//Additional class methods

What is metaclass

Metaclasses are the "things" used to create classes. Metaclasses are used to create class objects, and metaclasses are classes of classes.
It can be understood as follows:

MyClass =   MetaClass() # Creating class objects using metaclasses
MyObject = MyClass() # Creating instance objects using class objects

The type function is actually a metaclass. Type is the metaclass of all classes created in Python. It can be viewed through the _class_ attribute. The function of _class_ is to view the class of the object, which can be nested.

class A:
    pass
print(A.__class__)
a = A()
print(a.__class__)
print(a.__class__.__class__)
# Output results:
<class 'type'>
<class '__main__.A'>
<class 'type'>

As you can see, the final object class is the type metaclass.
Everything in Python, notice, I mean everything -- objects. This includes integers, strings, functions, and classes. They are all objects, and they are all created from a class, which is type.
Integer:

age = 18
print(age.__class__)
print(age.__class__.__class__)
# Output results:
<class 'int'>
<class 'type'>

Character string:

name = "Zhang San"
print(name .__class__)
print(name .__class__.__class__)
# Output results:
<class 'str'>
<class 'type'>

Function:

def f():
    pass
print(f.__class__)
print(f.__class__.__class__)
# Output results:
<class 'function'>
<class 'type'>

Custom metaclass

Let's first look at the metaclass attribute, which is used to specify a metaclass. python will look for the metaclass attribute in the defined class, and if it is not found, it will look for the metaclass attribute in its parent class and so on. If it is found, python will use it to create class objects, and if it is not found, it will use the built-in type to create the class.
In metaclass, you can put type or anything that uses type or subclassed type.
The main purpose of custom classes is:

  • Interception class creation
  • Modifying classes

    Implementing a custom metaclass with functions

    Function: To capitalize class attribute names that are not _____________.
def upper_attr(future_class_name: str,future_class_parents: tuple,future_class_attr: dict):
    newAttr = {}
    for key,value in future_class_attr.items():
        if not key.startswith("__"):
            newAttr[key.upper()] = value
    return type(future_class_name,future_class_parents,newAttr)
class Foo(metaclass=upper_attr):
    name = "Zhang San"
    age = 18

hasattr(Foo,"name") # Determine whether there is such a property False
hasattr(Foo,"NAME") # True
hasattr(Foo,"age") # False
hasattr(Foo,"AGE") # True

Implementing a custom metaclass by inheriting type

Function: Ibid.

class MyMetaClass(type):
    def __new__(cls, class_name: str, class_parents: tuple, class_attr: dict):
        newAttr = {}
        for key, value in class_attr.items():
            if not key.startswith("__"):
                newAttr[key.upper()] = value

        # Method 1: Create class objects by'type'
        # return type(class_name, class_parents, newAttr)
    
        # Method 2: Reuse type. _new_ method
        # That's basic OOP programming, no magic.
        # return type.__new__(cls, class_name, class_parents, newAttr)
    
        # Method 3: Using super method
        return super(MyMetaClass, cls).__new__(cls, class_name, class_parents, newAttr)


class Foo(metaclass=MyMetaClass):
    name = "Zhang San"
    age = 18

hasattr(Foo,"name") # Determine whether there is such a property False
hasattr(Foo,"NAME") # True
hasattr(Foo,"age") # False
hasattr(Foo,"AGE") # True

The effect is the same as above.

Posted by sgarcia on Wed, 21 Aug 2019 01:42:14 -0700