table of Contents
type() dynamically creates a class
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 int
and 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 Enum
classes 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
value
Attributes are int
constants automatically assigned to members , 1
counting from the beginning by default .
If you need to control the enumeration type more precisely, you can Enum
derive 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
@unique
Decorators 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 Hello
class 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:
- the name of the class;
- 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 ;
- The method name of the class is bound to the function, here we
fn
bind the function to the method namehello
.
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 add
method 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 MyList
it 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:
-
Objects of the class currently to be created;
-
The name of the class;
-
The collection of parent classes inherited by the class;
-
The collection of methods of the class.
Test MyList
whether 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 Field
class, 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 Field
the basis of this, further define various types Field
, such as StringField
, IntegerField
etc.:
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 User
searches in the definition of the current class metaclass
. If it is not found, it continues Model
to search in the parent class. If it metaclass
finds one, it uses the Model
one defined metaclass
in ModelMetaclass
to create the User
class, 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:
-
Exclude
Model
modifications to the class; -
User
Find 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); -
Save the table name
__table__
in, here is simplified to the table name and default to the class name.
In Model
class, the method can define various database operations, such as save()
, delete()
, find()
, update
and 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 INSERT
statement 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]