Python beginner's prerequisite: do you understand metaclasses?

Class is also an object

Before you understand metaclasses, you need to master the classes in Python.

The concept of classes in Python is a bit strange in light of Smalltalk. In most programming languages, a class is a set of code snippets that describe how to generate an object.

This is still true in Python:

>>> class ObjectCreator(object):
...       pass
...
​
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

However, classes in Python go far beyond that. Class is also an object. Yes, that's right. It's the object. As long as you use the keyword class, the Python interpreter creates an object when it executes.

Here's the code:

class ObjectCreator(object):
      pass

An object will be created in memory with the name ObjectCreator. This object (class) itself has the ability to create objects (class instances), which is why it is a class. However, its essence is still an object, so you can do the following for it:

  • You can assign it to a variable
  • You can copy it
  • You can add attributes to it
  • You can pass it as a function parameter

Here is an example:

>>> print ObjectCreator     # You can print a class because it's also an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print o
...
>>> echo(ObjectCreator)                 # You can pass a class as an argument to a function
<class '__main__.ObjectCreator'>
>>> print hasattr(ObjectCreator, 'new_attribute')
Fasle
>>> ObjectCreator.new_attribute = 'foo' #  You can add properties to a class
>>> print hasattr(ObjectCreator, 'new_attribute')
True
>>> print ObjectCreator.new_attribute
foo
>>> ObjectCreatorMirror = ObjectCreator # You can assign a class to a variable
>>> print ObjectCreatorMirror()
<__main__.ObjectCreator object at 0x8997b4c>

Create classes dynamically

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

>>> def choose_class(name):
...       if name == 'foo':
...           class Foo(object):
...               pass
...           return Foo     # Class is returned, not an instance of class
...       else:
...           class Bar(object):
...               pass
...           return Bar
...
>>> MyClass = choose_class('foo')
>>> print MyClass              # Function returns a class, not an instance of a class
<class '__main__'.Foo>
>>> print MyClass()            # You can use this class to create class instances, that is, objects
<__main__.Foo object at 0x89c6d4c>

But it's not dynamic enough, because you still need to write your own code for the entire class. Because classes are also objects, they must be generated by something.

type can create a class dynamically. type can take the description of a class as a parameter, and then return a class.

type: class name, tuple of parent class (can be empty for inheritance), dictionary containing attributes (name and value) For example, the following code:

>>> MyShinyClass = type('MyShinyClass', (), {})  # Returns a class object
>>> print MyShinyClass
<class '__main__.MyShinyClass'>
>>> print MyShinyClass()  #  Create an instance of this class
<__main__.MyShinyClass object at 0x8997cec>
type Accept a dictionary to define properties for the class, so:
>>> class Foo(object):
...       bar = True

Equivalent to:

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

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

Metaclass

Metaclasses are things that create objects like classes. If you like, you can call metaclasses "class factories.". type is Python's built-in metaclass. Of course, you can also create your own metaclass.

Metaclasses themselves are very simple:

  • Creation of interception class
  • Modify class
  • Return the modified class

1. Custom metaclass

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

Syntax for creating metaclasses in Python 3:

class NothingMetaclass(type):
    def __new__(mcs, name, bases, namespace):
         # Nothing. You can do something here
        return type.__new__(mcs, name, bases, namespace)
​
class Foo(object, metaclass=NothingMetaclass):
    pass

2,demo1

  • new is a static method, and init is an instance method
  • The new method returns a created instance, and init returns nothing
  • The subsequent init can only be called when new returns an instance of cls
  • Call new when creating a new instance and init when initializing an instance

Take an example

# Remember, 'type' is actually a class, just like 'str' and 'int'
# So, you can inherit from type
class MetaA(type):
    # Wei new__  Yes__ init__ Special methods previously called
    # Wei 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
    # You rarely use it__ new__ , unless you want to be able to control the creation of objects
    # Here, the created object is a class, and we want to be able to customize it, so we rewrite it here__ new__
    # If you want, you can also__ init__ Do something
    # There are also advanced uses that involve rewriting__ call__ Special methods, but we don't use them here
    def __new__(cls, name, bases, dct):
        print('MetaA.__new__')
        # This method does not call__ init__ method
        # return type(name, bases, dct)
        # This will call__ init__
        return type.__new__(cls, name, bases, dct)
​
    def __init__(cls, name, bases, dct):
        print('MetaA.__init__')
​
​
class A(object, metaclass=MetaA):
    pass
​
​
print(A())
3,demo2
class ListMetaclass(type):
​
    # Metaclasses automatically pass in the parameters you usually pass to 'type' as their own parameters
    # mcs represents metaclass
    # Name refers to the class name of the created class (in this case, the User subclass inherits the Model class)
    # bases means to create all the parent classes inherited by the class
    # namespace represents all the properties and methods of the created class (in the form of a dictionary of key value pairs)
    def __new__(mcs, name, bases, namespace):
        namespace['add'] = lambda self, value: self.append(value)
        return type.__new__(mcs, name, bases, namespace)
​
​
# Through metaclass, add method is added to the class dynamically
class MyList(list, metaclass=ListMetaclass):
    pass
​
​
l = MyList()
l.add(1)
print(l)

4. Why use metaclasses?

Now back to our main topic, why do you use such an error prone and obscure feature? Well, in general, you don't use it at all:

"Metaclasses are the magic of depth. 99% of users should not worry about it at all. If you want to know if you need a metaclass, you don't need it. Those who actually use metaclasses know exactly what they need to do, and they don't need to explain why they use metaclasses at all. " ——Tim Peters, the leader of Python The primary purpose of metaclasses is to create API s. A typical example is Django ORM.

It allows you to define it like this

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

You can then manipulate the database through a simple point API. The magic behind it is to define the metaclass, and use some magic to turn the simple Person class you just defined into a complex hook of the database.

The Django framework simplifies these seemingly complex things by exposing a simple API that uses metaclasses, recreates the code through this API, and completes the real work behind it.

A simple orm demo:

class Field:
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type
​
    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)
​
​
class StringField(Field):
    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')
​
​
class IntegerField(Field):
    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')
​
​
class ModelMetaclass(type):
    def __new__(mcs, name, bases, attrs):
        if name == 'Model':
            return type.__new__(mcs, name, bases, attrs)
        print("Found Model: %s" % name)
        mapping = dict()
        fields = list()
        # Save properties to mapping
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping : %s ==> %s' % (k, v))
                mapping[k] = v
                fields.append(k)
        # Delete Field in Model
        for k in mapping.keys():
            attrs.pop(k)
​
        attrs['__fields__'] = list(map(lambda f: '`%s`' % f, fields))
        attrs['__mapping__'] = mapping
        attrs['__table__'] = name
        return type.__new__(mcs, name, bases, attrs)
​
​
class Model(dict, metaclass=ModelMetaclass):
    def __init__(self, **kwargs):
        super(Model, self).__init__(kwargs)
​
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)
​
    def __setattr__(self, key, value):
        self[key] = value
​
    def save(self):
        fields = []
        params = []
        args = []
​
        for k, v in self.__mapping__.items():
            print("%s------%s" % (k, v))
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self, k, None))
​
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(self.__fields__), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))
​
​
class User(Model):
    # Define the attribute to column mapping for the class:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')
​
​
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
u.save()

epilogue

Everything in Python is an object, either an instance of a class or an instance of a metaclass, except for type.

type is actually its own metaclass, which you can't do in a pure Python environment. It's done through some small means at the implementation level. Second, metaclasses are complex. For very simple classes, you may not want to make changes to the class by using metaclasses.

There are two other techniques you can use to modify classes:

  • Monkey patching
  • Class decorator

When you need to dynamically modify a class, you'd better use these two techniques 99% of the time. Of course, you don't need to change classes dynamically 99% of the time.

Source network, for learning purposes only, invasion and deletion.

Don't panic. I have a set of learning materials, including 40 + E-books, 800 + teaching videos, involving Python foundation, reptile, framework, data analysis, machine learning, etc. I'm not afraid you won't learn! https://shimo.im/docs/JWCghr8prjCVCxxK/ Python learning materials

Pay attention to the official account [Python circle].

file

Tags: Programming Python SQL Lambda Django

Posted on Mon, 18 May 2020 02:07:56 -0700 by RyanSmith345