Python-use enumeration class enum/type to dynamically create class/metaclass metaclass

table of Contents

 

Enumeration class enum

type() dynamically creates a class

Metaclass


Enumeration class enum

When we need to define constants, one way is to use uppercase variables to define integers, such as months:

JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12

The advantage is simplicity, the disadvantage is that the type is intand is still a variable.

A better way is to define a class type for such an enumeration type, and then each constant is a unique instance of the class. Python provides Enumclasses to achieve this function:

from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)
Jan => Month.Jan , 1
Feb => Month.Feb , 2
Mar => Month.Mar , 3
Apr => Month.Apr , 4
May => Month.May , 5
Jun => Month.Jun , 6
Jul => Month.Jul , 7
Aug => Month.Aug , 8
Sep => Month.Sep , 9
Oct => Month.Oct , 10
Nov => Month.Nov , 11
Dec => Month.Dec , 12

valueAttributes are intconstants automatically assigned to members , 1counting from the beginning by default .

If you need to control the enumeration type more precisely, you can Enumderive a custom class from:

from enum import Enum, unique
@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6
#调用枚举成员的 3 种方式
>>>print(print(Weekday.Tue))
Weekday.Tue
>>>print(Weekday['Tue'])
Weekday.Tue
>>>print(Weekday(1))
Weekday.Mon

#调取枚举成员中的 value 和 name
>>>print(Weekday.Mon.value)
1
>>>print(Weekday.Mon.name)
Mon

#遍历枚举类中所有成员的2种方式
>>> for name, member in Weekday.__members__.items():
...     print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat

>>>for weekday in Weekday:
...    print(weekday)
Weekday.Sun
Weekday.Mon
Weekday.Tue
Weekday.Wed
Weekday.Thu
Weekday.Fri
Weekday.Sat

@uniqueDecorators can help us check to ensure that there are no duplicate values.

Enumeration class members cannot be compared, but you can use == or is to compare whether they are equal

type() dynamically creates a class

type()A function can return the type of an object, and it can also create a new type . For example, we can type()create a Helloclass through a function without having to pass class Hello(object)...the definition:

>>> def fn(self, name='world'): # 先定义函数
...     print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>

 To create a class object, the type()function passes in 3 parameters in turn:

  1. the name of the class;
  2. Collection of inherited parent classes. Note that Python supports multiple inheritance. If there is only one parent class, don’t forget the single element writing of tuple ;
  3. The method name of the class is bound to the function, here we fnbind the function to the method name hello.

Metaclass

Generally, we define the class first, and then create an instance. But we can create a class based on the metaclass, so: first define the metaclass, you can create the class, and finally create the instance. The metaclass allows you to create or modify classes. In other words, you can think of a class as an "instance" created by a metaclass .

Eg, metaclass can add a addmethod to our custom MyList :

1. DefinitionListMetaclass . According to the default habit, the class name of a metaclass always ends with Metaclass, in order to clearly indicate that this is a metaclass:

# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

2. When we define the class, we also instruct to use ListMetaclass to customize the class and pass in the keyword parametersmetaclass :

class MyList(list, metaclass=ListMetaclass):
    pass

When we pass in the keyword parameters metaclass, the magic takes effect. It instructs the Python interpreter to create MyListit through ListMetaclass.__new__()when it is created. Here, we can modify the definition of the class, for example, add a new method, and then return to modify After the definition.

__new__()The parameters received by the method are:

  1. Objects of the class currently to be created;

  2. The name of the class;

  3. The collection of parent classes inherited by the class;

  4. The collection of methods of the class.

Test MyListwhether the add()method can be called :

>>> L = MyList()
>>> L.add(1)
>>> L
[1]

The main purpose of metaclasses is to create APIs, and ORM is a typical example. The full name of ORM is "Object Relational Mapping", that is, object-relational mapping, which is to map a row of a relational database to an object, that is, a class corresponds to a table. In this way, it is easier to write code without directly operating SQL statements. To write an ORM framework, all classes can only be dynamically defined, because only the user can define the corresponding class according to the structure of the table.

 

First, define the Fieldclass, which is responsible for storing the field names and field types of the database table:

class Field(object):

    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)

On Fieldthe basis of this, further define various types Field, such as StringField, IntegerFieldetc.:

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')

The next step is to write the most complex ModelMetaclass:

class ModelMetaclass(type):

    def __new__(cls, name, bases, attrs):
        if name=='Model':
            return type.__new__(cls, name, bases, attrs)
        print('Found model: %s' % name)
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mappings # 保存属性和列的映射关系
        attrs['__table__'] = name # 假设表名和类名一致
        return type.__new__(cls, name, bases, attrs)

And the base class Model:

class Model(dict, metaclass=ModelMetaclass):

    def __init__(self, **kw):
        super(Model, self).__init__(**kw)

    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.__mappings__.items():
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self, k, None))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

When the user defines one class User(Model), the Python interpreter first Usersearches in the definition of the current class metaclass. If it is not found, it continues Modelto search in the parent class. If it metaclassfinds one, it uses the Modelone defined metaclassin ModelMetaclassto create the Userclass, that is, metaclass can be hidden Inherited to the subclass in a way, but the subclass itself does not feel it.

In ModelMetaclass, a total of several things have been done:

  1. Exclude Modelmodifications to the class;

  2. UserFind all the attributes of the defined class in the current class (for example ). If a Field attribute is found, it will be saved to a __mappings__dict, and the Field attribute will be deleted from the class attribute at the same time. Otherwise, it is easy to cause runtime errors (example The attribute of will cover the attribute of the same name of the class);

  3. Save the table name __table__in, here is simplified to the table name and default to the class name.

In Modelclass, the method can define various database operations, such as save(), delete(), find(), updateand the like.

We implemented the save()method to save an instance in the database. Because of the table name, attribute-to-field mapping, and attribute value collection, a INSERTstatement can be constructed .

Try writing code:

u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')
u.save()

The output is as follows:

Found model: User
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
Found mapping: id ==> <IntegerField:uid>
Found mapping: name ==> <StringField:username>
SQL: insert into User (password,email,username,id) values (?,?,?,?)
ARGS: ['my-pwd', '[email protected]', 'Michael', 12345]

Guess you like

Origin blog.csdn.net/zangba9624/article/details/106326004