Python中的類和元類(metaclass)以及黑魔法(__metaclass__)
一、Python中的類
首先在這裡討論的python類,都是基於新式類進行討論。
在python中,一切皆為物件。
在理解元類之前我們先來重新理解一下python中的類。
class Joker:
pass
當Python在執行帶class語句的時候,會初始化一個類物件放在記憶體裡面。例如這裡會初始化一個Joker物件,這個類物件自身擁有建立例項物件的能力。
為了方便後續理解,我們可以嘗試一下在新式類中的關鍵字type。
class Joker: pass print(type('123')) print(type(123)) print(type(Joker())) >>> <type 'str'> >>> <type 'int'> >>> <class '__main__.Joker'>
我們可以看到,type可以檢視一個物件的型別,也能夠檢視建立這個例項物件的類。
但是下面的方法你可能沒有見過,type同樣可以用來動態建立一個類。
type(類名,父類的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值))
這個怎麼用呢,我們可以用這個方法建立一個類:
print(type('Joker', (), {}))
>>> <class '__main__.Joker'>
同樣我們可以例項化這個類物件:
print(type('Joker', (), {})()) >>> <__main__.joker object at xxxxxxxxx>
可以看到,這裡就是一個joker的例項物件。
同樣的這個方法還可以初始化建立類的父類,同時也可以初始化類屬性:
class Done: pass dn = type('Joker', (Done, ), {'sleep': 'zzz'}) print(dn().sleep) print(dn.__dict__) print(dn.__bases__) print(dn().__class__) print(dn().__class__.__class__) >>> zzz >>> {'__module': '__main__', 'sleep': 'zzz', '__doc__': None} >>> (<class '__main__.Done'>, ) >>> <class '__main__.Joker'> >>> <type 'type'>
下面我們先來了解幾個魔法方法:
1. 方法__class__用於檢視物件是由誰建立的,類物件也是物件。類是元類的例項,而例項物件則是類的例項。
2. 方法__bases__用於得到一個物件的父類是誰,特別注意一下__base__返回單個父類,__bases__以元組的形式返回所有父類。
下面我來解釋一下上面程式碼的執行結果:
1. 使用type建立一個類賦值給dn,type接收的三個引數的意思分別為(類的名稱,類的父類(可以為空),類的屬性(字典形式))。
2. 初始化類的例項,然後嘗試去獲得類屬性的sleep屬性。
3. 獲取dn這個類的所有屬性(字典形式)。
4. 獲取dn這個類的父類集合。
5. 獲取建立dn()這個例項物件的類。
6. 獲取建立dn()這個例項物件的類的類(有點繞,即獲取例項化Joker這個類的類)。
二、什麼是元類以及簡單的運用
介紹完上面的內容後我們開始進入主要環節:
那到底什麼是元類呢?
通俗的說,元類就是建立類的類,是不是還是有點抽象?
不要慌,聽我慢慢道來:
Joker = MetaClass()
MyObject = Joker()
上面我們已經介紹了,建立一個類可以直接這樣搞:
Joker = type('Joker', (), {})
那為什麼可以這樣來搞呢?
type實際上是一個元類,用它可以去建立類。
也可以說元類實際上就是一個建立類的工廠。
那麼類裡面的__metaclass__屬性,相信很多小朋友對這個很好奇,使用了它實際上就意味著會用__metaclass__指定的元類來建立類了。
class Joker(Done):
pass
當我麼在建立上面的類的時候,Python做了如下的操作:
首先,Python會先去Joker裡面找是否有__metaclass__這個屬性,如果有,Python會在記憶體中通過__metaclass__建立一個名字為Joker的類物件,也就是Joker;如果沒有找到__metaclass__,它會繼續在自己的父類Done中尋找__metaclass__屬性,並且嘗試以__metaclass__指定的方法建立一個Joker類物件。
如果Python在任何一個父類中都沒有找到__metaclass__屬性,它會去模組中搜尋是否有__metaclass__屬性。如果還是找不到,Python會是哦用預設的type來建立Joker。
那麼問題來了,__metaclass__屬性中設定的是什麼呢?
答案是可以建立一個類的東西:type或者任何用到type或子類化type的東西都行。
三、自定義元類
自定義元類的目的,就是攔截類的建立,然後修改一些特性,然後返回該類。
是不是感覺和某種東西很像?沒錯,它和裝飾器很像,為特定的物件附加額外的功能,或者修改某些功能,最後被返回。
其實除了上面談到的指定一個__metaclass__,我們給它賦值的不一定是一個正式的類,函式也可以。
要建立一個使所有模組級別都是用這個元類建立的話,我們只需要在模組級別設定__metaclass__就可以了。
下面我們來寫個例子,將建立的類裡面的所有屬性名都改為大寫。
def upper_attr(class_name, class_parents, class_attr):
'''
返回一個物件,將它的屬性都改為大寫
:param class_name: 類的名稱
: param class_parents: 類的父類tuple
: param class_attr: 類的引數dict
: return: 類
'''
# 使用一個generator來儲存類的引數
attrs = ((name, value) for name, value in class_attr.items() if not name.startswith('__'))
# 將屬性名全部改為大寫並轉化為dict
uppercase_attrs = dict((name.upper(), value) for name, value in attrs)
return type(class_name, class_parents, uppercase_attrs)
# 設定全域性變數
__metaclass__ = upper_attr
# 建立類
upa = upper_attr('Joker', (), {'bar': 0})
print(hasattr(upa, 'bar'))
print(hasattr(upa, 'BAR'))
print(upa.BAR)
>>> False
>>> True
>>> 0
我們可以看到在上面的程式碼中,實現了一個元類(metaclass),然後指定了模組使用這個元類來建立類,所以當我下面使用type進行建立的時候,可以發現小寫的bar引數被替換成了大寫的BAR引數,並且在最後可以呼叫這個被修改後的引數。
上面我們使用函式作為元類傳遞給類,下面我們使用一個正式類來作為元類傳遞給__metaclass__
class UpperAttrMetaClass(type):
# 注意它的第一個引數為mcs,這個和類方法用cls、例項方法用self是同一個道理
def __new__(mcs, class_name, class_parents, class_attr):
attrs = ((name, value) for name, value in class_attr if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return super(UpperAttrMetaClass, mcs).__new__(mcs, class_name, class_parents, uppercase_attr)
class Joker:
__metaclass__ = UpperAttrMetaClass
bar = 12
money = 'unlimited'
print(Joker.BAR)
print(Joker.MONEY)
>>> 12
>>> unlimited
總結
這些都是我在蒐集了諸多資料之後自行整理的一些筆記,備忘的同時希望也能分享給大家,這個知識點大家沒有必要去死記硬背,在需要的時候可以回過頭來複習一下,平時的話很少有用到的地方,但是它能讓你瞭解在Python當中類建立的這麼一個過程,加深大家對Python的理解。