Python的Metaclass魔法

經過type建立Class

衆所周知,在Python編程中,經過class定義類,再經過類實例化生成實例對象,全部實例對象都繼承自object對象。但其實class自己也是一個對象,咱們稱之爲類對象,全部class對象繼承自type。
咱們經過如下簡單代碼在Python交互式CLI中進行測試:html

# 定義類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)的建立過程,從而對類對象的建立過程進行定製和設計呢?答案是可定的。python

type自己也是一個類(class),它不但能判斷一個對象的類型,還能夠建立類對象。sql

  • type(obj):判斷一個對象的類型
  • type(name, bases, attrs):建立一個新類對象。

三個參數描述以下:數據庫

  1. name:類的名稱,str類型
  2. bases:此類集成的父類集合,tuple類型
  3. attrs:類的屬性列表,dict類型

經過type建立類示例:django

# 定義一個方法
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。class是實例對象(instance)的模板,而Metaclass則是class的模板,三者間的關係描述以下圖。編程

+----------+             +----------+             +----------+
|          |             |          |             |          |
|          | instance of |          | instance of |          |
| instance +------------>+  class   +------------>+ metaclass|
|          |             |          |             |          |
|          |             |          |             |          |
+----------+             +----------+             +----------+

經過這種方式,在建立類對象前,Python先會執行Metaclass的相關方法來定製類對象,從而達到對類進行動態定製的目的,Django中的ORM框架,就是經過這種方式來實現的。這種定製,包括給類增長屬性、方法,對類進行二次處理等。app

下面演示經過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框架。函數

Metaclass的尋找順序:
類建立時,先查找類自己是否設置了Metaclass,若是設置了,則直接調用Metaclass建立類;若是沒有,則根據基礎順序,一路找到基類,若是找到,則調用父類的Metaclass建立類;若是都沒找到,則調用type建立類。

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

根據設計思路,功能完善後的代碼以下:

  • ModelMetaclass主要解析Model中的Field類型字段,生成ORM數據庫表字段的元數據。
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)
  • Model類做爲全部ORM模型的基類,爲模型提供通用功能。

    1. __init__爲模型構造函數,處理傳入的Field字段
    2. __setattr____getattr__讓模型Field類型字段能夠像普通字段同樣進行設值和取值
    3. save方法用於產生把模型保存到數據庫的SQL語句
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
            return
        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.setdefault(field_key, None))
            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)
  • Field類及其子類、模型類
class Field:
    pass
    
class CharField(Field):
    pass
    
class DateTimeField(Field):
    pass
    
class Question(Model):
    question_text = CharField()
    pub_date = DateTimeField()
  • ORM使用示例、執行輸出
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語句和參數,只要提交到數據庫就能夠實際運行了。

結束語

經過以上幾個簡單的示例,相信你們對Metaclass的使用應該有了一個更深的瞭解,不過要理解代碼,最好仍是本身動手編碼、調試,紙上得來終覺淺,絕知此事要躬行
得益於Python的動態語言特性的靈活性和精巧設計,咱們經過不到100行代碼,就實現了一個ORM框架原型,和Java等重量級編譯型語言相比,各方面都要更勝一籌。
不過這只是一個簡易的示例,實際框架還有不少的工做要作。

參考資料:

相關文章
相關標籤/搜索