python是个神奇的语言,神奇的地方在于一切皆对象,所以没有对象的你在这里应该可以找到真爱。
什么叫一切皆对象?什么是对象?它和类class有什么关系?
在python里我们定义类的时候往往会
class A(object):
pass
这个object我们都知道是父类,任何类往上倒几代,祖先都是object。
那我们定义的这些类的类型是什么呢?
print(type(A))
<class 'type'>
类的type是type,哈哈哈。
python里类型的尽头是type,不信你看
a = 1
print(type(a)) # int
print(type(type(a))) # type
因此有如下说法:
type为对象的顶点,所有对象都创建自type。
object为类继承的顶点,所有类都继承自object。
我们知道在定义类的时候需要定义__init__
方法,用于在实例化的时候进行参数初始化的,但真正的对象创建却不是在这个方法里,而是在__new__
里进行定义,因此我们可以进行__new__
方法的重构而对对象创建的过程进行干预。
铺垫了半天metaclass到底是干嘛的?既然类是对象,大家想一下我们在定义类(对象创建)的时候能不能干涉一下?这就是metaclass的作用。看个例子
class A(type):
def __new__(cls, *args, **kwargs):
print('metaclass new')
return type.__new__(cls, *args, **kwargs)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print('metaclass init')
class B(metaclass=A):
def __init__(self):
print('B init')
print('==start==')
b = B()
输出
metaclass new
metaclass init
==start==
B init
大家注意一下打印的顺序,在还没有实例化b的时候,metaclass就已经执行了,也就是说class B生成的时候(不是实例化)受到了class A的影响。
那这到底有啥用?什么场景会用到?
我们在使用Django框架的时候,不可避免会涉及到数据的管理操作(CRUD),而且需要与数据库进行连接操作。而关系型的数据库需要编写原生的SQL语句,那么如果我们的代码里包含大量的sql语句会严重影响开发效率,且变得难以维护。
因此Django提出了ORM概念,在SQL语句上进行了面向对象的封装,现在我们来实现一下:
# 一、首先定义Field类,它负责保存数据库表的字段名和字段类型
class Field:
def __init__(self, name, column_type):
self.name = name
self.colmun_type = column_type
def __str__(self):
return f'<{self.__class__.__name__}:{self.name}>'
class StringField(Field):
def __init__(self, name):
super().__init__(name, 'varchar(100)')
class IntegerField(Field):
def __init__(self, name):
super().__init__(name, 'bigint')
# 二、定义元类,控制Model对象的创建
class ModelMetaClass(type):
def __new__(cls, name, bases, attrs):
if name == 'Model':
return super().__new__(cls, name, bases, attrs)
mappings = dict()
for k, v in attrs.items():
# 保持类属性和列的映射关系到mappings字典
if isinstance(v, Field):
print(f'Found mapping:{k}==>{v}')
mappings[k] = v
for k in mappings.keys(): # 将类属性移除,是定义的类字段不污染User类属性,只在实例中可以访问这些key
attrs.pop(k)
attrs['__table__'] = name.lower() # 假设表名为类名的小写,创建类时添加一个__table__属性
attrs['__mappings__'] = mappings # 保持属性和列的关系映射,创建类时添加一个__mappings__属性
return super().__new__(cls, name, bases, attrs)
# 三、Model基类
class Model(dict, metaclass=ModelMetaClass):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(f"'Model' object has no attribute '{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 = f"insert into {self.__table__} {','.join(fields)} values ({','.join(params)})"
print(f'SQL:{sql}')
print(f'ARGS:{str(args)}')
# 我们想创建类似Django的ORM,只要定义字段就可以实现对数据库表和字段的操作
# 最后、我们使用定义好的ORM接口,使用起来非常简单
class User(Model):
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
user = User(id=1, name='Job', email='[email protected]', password='pw')
user.save()
输出
Found mapping:id==><IntegerField:id>
Found mapping:name==><StringField:username>
Found mapping:email==><StringField:email>
Found mapping:password==><StringField:password>
SQL:insert into user id,username,email,password values (?,?,?,?)
ARGS:[1, 'Job', '[email protected]', 'pw']
Model基类通过元类ModelMetaClass
控制对象的创建,然后通过定义统一的save接口从而在用户定义数据库和操作的时候会十分容易且人性化,然后我们再将update、delete和search接口实现一下即可。
元类同时实现__getattr__
和__setattr__
方法,可以直接引用普通字段
user.id = 1
print(user.id)
是不是可以不用metaclass,直接在Model里重构__new__
,内容与ModelMetaClass
的__new__
一致,能否达到一样的效果?
不行的,只有metaclass的__new__
才能对子类的传参进行修改,其他类是不可以的,因此只能使用metaclass
配置文件与类相结合,通过配置文件进行类实例化,不同的配置文件可以进行不同的实例化,这里的yaml.YAMLObject
的实现也是使用了metaclass。
import time
import yaml
class Monster(yaml.YAMLObject):
yaml_tag = '!yaml'
def __init__(self, name):
self.name = name
while 1:
with open('dynamicLoad_demo.yaml', 'r') as f:
cls = yaml.load(f, Loader=yaml.Loader)
print(cls.name)
time.sleep(2)
同时构建配置文件dynamicLoad_demo.yaml
!yaml
name:
zzr
代码跑起来,过一会修改配置文件中的name字段,可以看到输出同步发生变化。
python中的metaclass