通过 type 创立 Class
家喻户晓,在 Python 编程中,通过 class 定义类,再通过类实例化生成实例对象,所有实例对象都继承自 object 对象。但其实 class 自身也是一个对象,咱们称之为类对象,所有 class 对象继承自 type。
咱们通过以下简略代码在 Python 交互式 CLI 中进行测试:
# 定义类 A
>>> class A(object):>>> pass
>>> type(A)
<class 'type'>
>>> type(A())
<class '__main__.A'>
>>> isinstance(A, type)
True
>>> isinstance(A(), object)
True
咱们能够通过类(class)对实例对象(instance object)进行定制和设计,管制实例对象的创立过程,那么咱们是否能通过 type 管制类对象(class object)的创立过程,从而对类对象的创立过程进行定制和设计呢?答案是可定的。
type 自身也是一个类(class),它岂但能判断一个对象的类型,还能够创立类对象。
- type(obj):判断一个对象的类型
- type(name, bases, attrs):创立一个新类对象。
三个参数形容如下:
- name:类的名称,str 类型
- bases:此类集成的父类汇合,tuple 类型
- attrs:类的属性列表,dict 类型
通过 type 创立类示例:
# 定义一个办法
def greeting(self, name='world'):
print("Hello, %s." % name)
# 通过 type 创立类
Hello = type("Hello", (object,), {"greeting": greeting})
h = Hello()
h.greeting() # >> Hello, world.
print(type(Hello)) #>> <class 'type'>
print(type(h)) # >> <class '__main__.Hello'>
Metaclass 的应用
岂但能够通过 type 动静的创立类,还能够通过继承 type
创立一个元类 Metaclass,把此 Metaclass 类作为其余类的 metaclass。通过这种形式,在创立类对象前,Python 先会执行 Metaclass 的相干办法来定制类对象,从而达到对类进行动静定制的目标,Django 中的 ORM 框架,就是通过这种形式来实现的。这种定制,包含给类减少属性、办法,对类进行二次解决等。
上面演示通过 Metaclass 给自定义 list 类减少 insert_before
办法的过程。
class ListMetaclass(type):
def __new__(mcs, name, bases, attrs):
attrs["insert_before"] = lambda self, item: self.insert(0, item)
return type.__new__(mcs, name, bases, attrs)
class NewList(list, metaclass=ListMetaclass):
pass
new_list = NewList(["a", "b"])
new_list.insert_before("insert_before")
print(new_list) # >> ['insert_before', 'a', 'b']
- 首先定义元类 ListMetaclass。
- class 的
__new__
办法是用来创立实例对象的,相似的 Metaclass 的__new__办法是用来创立类对象的。在创立类对象前,咱们通过在 attrs 中减少 insert_before 属性,这样创立的类对象中就领有此属性。 - 定义 NewList 类,并指定 metaclass。
- NewList 在创立类实例时,会调用 ListMetaclass 来创立此类实例。
注:
Metaclass.__new__
的参数阐明见上述 type 的参数阐明。
通过 Metaclass 实现 ORM
上面来看一个更理论可用的例子,以 Django Tutorial 中的展现的 ORM 的应用形式为例,通过 Metaclass 来开发一个繁难的 ORM 框架。
Django Tutorial 应用 ORM 次要包含三个步骤:
- 定义模型类
- 创立模型对象
- 保留模型数据到数据库
示例代码如下:
# 定义模型类
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
# 创立模型对象
q = Question(question_text="What's new?", pub_date=timezone.now())
# 保留模型数据到数据库
q.save()
ORM 框架的根本设计思路包含如下几点:
- 定义 Model 类作为所有模型的基类,所有模型对象继承此类。
- 定义 Field 类作为模型所有字段的基类,模型的属性(Field 字段)以类变量的模式定义在 Model 中。
- 定义 ModelMetaclass 类作为 Model 的 Metaclass,解析 Model 中的 Field 字段,进行预处理,保留为 Model 的元数据,供 ORM 映射时应用。
- 在 Model 中实现 save 办法,利用 Model 的元数据,主动拼装 Insert SQL 语言。
先搭个框架,再填充各局部性能。
from datetime import datetime
class ModelMetaClass(type):
def __new__(mcs, name, bases, attrs):
return type.__new__(mcs, name, bases, attrs)
class Model(metaclass=ModelMetaClass):
def __init__(self, **kwargs):
pass
def __setattr__(self, key, value):
pass
def __getattr__(self, item):
pass
def save(self):
pass
class Field:
pass
class CharField(Field):
pass
class DateTimeField(Field):
pass
class Question(Model):
question_text = CharField()
pub_date = DateTimeField()
question = Question(question_text="My first question.", pub_data=datetime.now())
question.save()
依据设计思路,功能完善后的代码如下:
from datetime import datetime
class ModelMetaClass(type):
def __new__(mcs, name, bases, attrs):
# 只解决 Model 的子类
if name == "Model" or Model not in bases:
return type.__new__(mcs, name, bases, attrs)
# 解决 Field 类型字段,信息保留在__fields__字段中
fields = dict()
for key in attrs:
if isinstance(attrs[key], Field):
fields[key] = attrs[key]
attrs["__fields__"] = fields
# 表名默认为 class 名的小写
attrs["__table__"] = name.lower()
# 删除 Field 类型的类变量
for key in fields:
attrs.pop(key)
return type.__new__(mcs, name, bases, attrs)
class Model(metaclass=ModelMetaClass):
def __init__(self, **kwargs):
# Field 字段数据保留在 self.fields 中
self.fields = dict()
# self.__fields__即 Metaclass 中的 attrs["__fields__"]字段
model_fields = self.__fields__
for kwarg_key in kwargs:
if kwarg_key in model_fields:
self.fields[kwarg_key] = kwargs[kwarg_key]
else:
raise KeyError()
def __setattr__(self, key, value):
# 实现通过 model.field = xxx 对 Field 字段赋值
if key in self.__fields__:
self.__dict__["fields"][key] = value
self.__dict__[key] = value
def __getattr__(self, key):
# 实现通过 model.field 读取 Field 字段值
if key in self.__fields__:
return self.__dict__["fields"][key]
return self.__dict__[key]
def save(self):
model_fields = self.__fields__
fields_key = list()
fields_value = list()
fields_placeholder = list()
for field_key in model_fields:
fields_key.append(field_key)
fields_value.append(self.fields[field_key])
fields_placeholder.append("?")
sql = "INSERT INTO %s (%s) VALUES (%s)" % (self.__table__, ",".join(fields_key), ",".join(fields_placeholder))
print("SQL:", sql)
print("ARGS:", fields_value)
class Field:
pass
class CharField(Field):
pass
class DateTimeField(Field):
pass
class Question(Model):
question_text = CharField()
pub_date = DateTimeField()
question = Question(question_text="My first question.", pub_date=datetime.now())
question.save()
# >> SQL: INSERT INTO question (question_text, pub_date) VALUES (?, ?)
# >> ARGS: ['My first question.', datetime.datetime(2020, 10, 13, 17, 52, 38, 443969)]
question.question_text = "My second question."
question.save()
# >> SQL: INSERT INTO question (question_text, pub_date) VALUES (?, ?)
# >> ARGS: ['My second question.', datetime.datetime(2020, 10, 13, 17, 52, 38, 443969)]
从打印输出可用看到,框架曾经打印出了 SQL 语句和参数,只有提交到数据库就能够理论运行了。不过这只是一个繁难的示例,理论框架还有很多的工作要做。
参考资料:
- What are metaclasses in Python?
- 应用元类
- 一篇文章让你彻底明确__getattr__、__getattribute__、__getitem__的用法与执行原理