通过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):创立一个新类对象。

三个参数形容如下:

  1. name:类的名称,str类型
  2. bases:此类集成的父类汇合,tuple类型
  3. 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']
  1. 首先定义元类ListMetaclass。
  2. class的__new__办法是用来创立实例对象的,相似的Metaclass的__new__办法是用来创立类对象的。在创立类对象前,咱们通过在attrs中减少insert_before属性,这样创立的类对象中就领有此属性。
  3. 定义NewList类,并指定metaclass。
  4. NewList在创立类实例时,会调用ListMetaclass来创立此类实例。
注:Metaclass.__new__的参数阐明见上述type的参数阐明。

通过Metaclass实现ORM

上面来看一个更理论可用的例子,以Django Tutorial中的展现的ORM的应用形式为例,通过Metaclass来开发一个繁难的ORM框架。

Django Tutorial应用ORM次要包含三个步骤:

  1. 定义模型类
  2. 创立模型对象
  3. 保留模型数据到数据库

示例代码如下:

# 定义模型类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框架的根本设计思路包含如下几点:

  1. 定义Model类作为所有模型的基类,所有模型对象继承此类。
  2. 定义Field类作为模型所有字段的基类,模型的属性(Field字段)以类变量的模式定义在Model中。
  3. 定义ModelMetaclass类作为Model的Metaclass,解析Model中的Field字段,进行预处理,保留为Model的元数据,供ORM映射时应用。
  4. 在Model中实现save办法,利用Model的元数据,主动拼装Insert SQL语言。

先搭个框架,再填充各局部性能。

from datetime import datetimeclass 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 datetimeclass 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:    passclass CharField(Field):    passclass DateTimeField(Field):    passclass 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__的用法与执行原理