一周一個Python語法糖:(三) 元類

先來了解下python魔法的內核吧:

一切皆對象

  • 一切皆對象
  • 一切皆有類型
  • “class” and "type" 之間本質上上并無不同
  • 類也是對象
  • 他們的類型都是type
class ObjectCreator(object):
        pass

這段代碼將在內存中創建一個對象,名字叫ObjectCreator.

這個對象(類)自身擁有創建對象的(類實例的能力),##

而這也就是它為什么是一個類的原因##

你可以對該對象進行以下操作:

  1. 將它賦值給一個變量
  2. 可以拷貝它
  3. 可以為它增加屬性
  4. 可以將其作為函數參數進行傳遞
#交互型
>>> def echo(o):
...     print(o)
... 
>>> echo(my_object)
<__main__.ObjectCreator object at 0x7fcf7d0d6c50>  #你可以將類做為參數傳給函數
>>> ObjectCreator.new_attrituer=echo    #  你可以為類增加屬性
>>> print(hasattr(ObjectCreator,'new_attrituer'))
True
>>> print(ObjectCreator.new_attrituer) #新加的類的方法屬性
<function echo at 0x7fcf7d171f28>

一:用type創建一個類

type(類名, 父類的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值))
#三個參數的類型分別是str,tuple,dic

比如:

#目標類:
class Animal(object):
    def __init__(self,name):
        self.name=name
    def eat(self):
        pass
    def go_to_eat(self):
        pass
#用type創建
def init(self,name):
    self.name=name
def eat(self):
    pass
def go_to_eat(self):
    pass
Animal=type('Animal',(object,),{
    '__init__':init,
    'eat':eat,
    'go_to_eat':go_to_eat
})

>>>print(Animal)
<class '__main__.Animal'>

這樣我們實現了用type去動態創建一個類的
BUT~這樣是不是太麻煩了!!!(抬走下一位!)

二:metaclass登場

metaclass,直譯為元類,簡單的解釋就是:

當我們定義了類以后,就可以根據這個類創建出實例,所以:先定義類,然后創建實例。

但是如果我們想創建出類呢?那就必須根據metaclass創建出類,所以:先定義metaclass,然后創建類。

連接起來就是:先定義metaclass,就可以創建類,最后創建實例。

所以,metaclass允許你創建類或者修改類。換句話說,你可以把類看成是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)
    
class Mylist(list):
    __metaclass__=Listmetaclass

  • 按照默認習慣.metaclass的類名總是以Metaclass結尾,以便清楚地表示這是一個metaclass
  • 當我們寫下__metaclass__=Listmetaclass時候,它表示解釋器在創建Mylist的時候
    ,要通過Listmetaclass的__new__()方法創建
    在此,我們可以修改類的定義,比如:加上新的方法,然后返回修改后的定義.
  • __new__()方法接受的參數依次是:
    1. 當前準備創建的類的對象
    2. 類的名字
    3. 類繼承的父類的集合
    4. 類的方法的集合

元類的主要目的就是為了當創建類時能夠自動改變類.

使用到元類的代碼比較復雜,這背后的原因倒并不是因為元類本身,

而是因為你通常會使用元類去做一些晦澀的事情,

依賴于自省,控制繼承等等。

確實,用元類來搞些“黑暗魔法”是特別有用的,

因而會搞出些復雜的東西來。

就這個例子來說:

  • 攔截類的創建(攔截Mylist的創建)
  • 修改類(給Mylist增加add的方法)
  • 返回修改之后的類

**“元類就是深度的魔法,99%的用戶應該根本不必為此操心。如果你想搞清楚究竟是否需要用到元類,那么你就不需要它。那些實際用到元類的人都非常清楚地知道他們需要做什么,而且根本不需要解釋為什么要用元類。” **-----Tim Peters

元類的主要作用的創建API,一個典型的例子是 ORM.

ORM全稱“Object Relational Mapping”,
即對象-關系映射,就是把關系數據庫的一行映射為一個對象,
也就是一個類對應一個表,
這樣,寫代碼更簡單,不用直接操作SQL語句。

~

要編寫一個ORM框架,所有的類都只能動態定義,
因為只有使用者才能根據表的結構定義出對應的類來。

我們來嘗試寫一個簡單的ORM框架吧
首先我們要知道使用者會調用什么接口:
比如,使用者可能會定義一個User類來操作數據表User.

class User(Model):
  #創建User表
  #創建四列屬性
    id=IntegerField('id')
    name=StringField('username')
    email=StringField('email')
    password=StringField('password')
    
# 創建一個實例:
u=User(id='12345',name='zhou',email='124@qq.com',password='123455')
#保存到數據庫
u.save()  

首先,我們來定義Field類,它負責保存數據庫表的字段名字跟字段類型:

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)

在Field的基礎上,我們來定義各種Field:

#為了簡化,我們先定義好各種屬性的type
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')

接下來,我們要編寫元類了

class ModelMetaclass(type):
    def __new__(cls,name,bases,attrs):
        #如果是基類,直接返回(即不對model類進行修改)
        if  name=='Model':
            return type.__new__(cls,name,bases,attrs)
        print('Found model: %s' %name)
        #如果是其他類,則進行裝飾
        #取出所有類屬性,將其放入mapping
        mappings=dict()
        for key,value in attrs.iteritems():
            if isinstance(value,Field):
                print('Found mapping: %s==>%s' %(key,value))
                mappings[key]=value
        for key in mappings.iterkeys():
            attrs.pop(key)
        attrs['__mappings__']=mappings
        attrs['__table__']=name
        return type.__new__(cls,name,bases,attrs)
        

編寫基類:

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 key,value in self.__mappings__.iteritems():
            fields.append(value.name)
            params.append('?')
            args.append(getattr(self,key,None))
        sql='insert into %s(%s) values(%s)' %(self.__table__,','.join(fields),','.join(params))
        print('SQL:%s' %sql)
        print('ARGS:%s' %str(args))

當用戶自定義一個class User(Model)時候,
解釋器首先在當前類User的定義中尋找
metaclass,如果沒找到,就在繼承的父類中繼續找.,
找到了,就用model中的定義的ModelMetaclass去創建User類
也就是說,metaclass可以隱式地繼承到子類,但子類自己卻感覺不到。

在ModelMetaclass中做的事情:

  • 排除對Model 的修改
  • 在當前類 中查找定義的類的屬性,如果找到一個FIeld屬性,就保存到__mappings__字典中
    同時從類屬性中刪除Field屬性,否則容易造成運行時的錯誤
  • 把表名保存到__table__中

在Model 類中,就可以定義各種操作數據庫的方法了(列屬性保存在__mappings__)中

全部代碼:

#!/usr/bin/python3
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)

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')
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 key,value in attrs.iteritems():
            if isinstance(value,Field):
                print('Found mapping: %s==>%s' %(key,value))
                mappings[key]=value
        for key in mappings.iterkeys():
            attrs.pop(key)
        attrs['__mappings__']=mappings
        attrs['__table__']=name
        return type.__new__(cls,name,bases,attrs)
        
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 key,value in self.__mappings__.iteritems():
            fields.append(value.name)
            params.append('?')
            args.append(getattr(self,key,None))
        sql='insert into %s(%s) values(%s)' %(self.__table__,','.join(fields),','.join(params))
        print('SQL:%s' %sql)
        print('ARGS:%s' %str(args))



class User(Model):
    id=IntegerField('id')
    name=StringField('username')
    email=StringField('email')
    password=StringField('password')
    
u=User(id='12345',name='zhou',email='124@qq.com',password='123455')
u.save()  
       

運行結果:

Found model: User
Found mapping: email==><StringField:email>
Found mapping: password==><StringField:password>
Found mapping: id==><IntegerField:id>
Found mapping: name==><StringField:username>
SQL:insert into User(password,email,username,id) values(?,?,?,?)
ARGS:['123455', '124@qq.com', 'zhou', '12345']

最后說幾句:

  • 元類(Metaclass)類似于裝飾器,可以在類的創建時動態修改類.

  • 元類語法糖:__metaclass__

學習參考:
廖雪峰Python教程
深入理解元類
5分鐘理解元類

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 什么是元類? 理解元類(metaclass)之前,我們先了解下Python中的OOP和類(Class) 面向對象全...
    時間之友閱讀 339評論 0 0
  • 1. 使用__slots__ 正常情況下,當我們定義了一個class,創建了一個class的實例后,我們可以給該實...
    時間之友閱讀 298評論 0 1
  • 前言 第十二篇了,擼起袖子,就是干。 目錄 一、Python 中類也是對象 在了解元類之前,我們先進一步理解 Py...
    GitHubClub閱讀 771評論 0 7
  • 我當然知道 那只是偶遇 可這也足夠 讓我歡喜 人生沒有多少必須 淡淡的感動 弱弱的情緒 就把生活裝點美麗 風暖菩提...
    微雨憑欄閱讀 151評論 0 0
  • 我有一位老師,看起來四十歲的樣子,卻仍舊身材姣好,氣質優雅,穿著打扮非常入時。身為女生,我們總會不住地感嘆:天吶,...
    稻場舊事閱讀 550評論 4 7