類元程式設計是指動態地建立或定製類,也就是在執行時根據不同的條件生成符合要求的類,一般來說,類元程式設計的主要方式有類工廠函式,類裝飾器和元類。
通常,我們都是使用 class 關鍵字來宣告乙個類,像這樣:
但是,我們還有另外一種方式來生成類,下述**與上面作用相同:class a:
name = 'a'
a = type('a', (object,), )
一般情況下我們把 type 視作函式,呼叫type(obj)
來獲取 obj 物件所屬的類。然而,type 是乙個類(或者說,元類,後面會介紹),傳入三個引數(類名,父類元組,屬性列表)便可以新建乙個類。至於類如何像函式一樣使用,只需要實現__call__
特殊方法即可。
在python中,類是一等物件,因此任何時候都可以使用函式建立類,而無需使用 class 關鍵字。
通常,我們定義乙個類需要用到 class 關鍵字,比如乙個簡單的 dog 類:
這樣乙個簡單的類,我們將每個欄位的名字都寫了三遍,並且想要獲得友好的字串表示形式還得再次編寫class dog:
def __init__(self, name, age, owner):
self.name = name
self.age = age
self.owner = owner
__str__
或者__repr__
方法,那麼有沒有簡單的方法即時建立這樣的簡單類呢?答案是有的。受到標準庫中的類工廠函式——collections.namedtuple的啟發,我們可以實現這樣乙個類似的工廠函式來建立簡單類:
dog = create_class('dog', 'name age owner')
實現這樣的工廠函式的思路也很簡單,切分出屬性名後呼叫 type 新建類並返回即可:
利用這樣的類工廠函式可以很方便的建立出類似dog的簡單類,並且擁有了友好的字串表示形式:def create_class(name, fields):
# 物件的屬性元組
fields = tuple(fields.replace(',', ' ').split())
def __init__(self, *args, **kwargs):
# attrs = dict(zip(self.__slots__, args))
# 關鍵字引數
attrs.update(kwargs)
for name, value in attrs.items():
# 相當於 self.name = value
setattr(self, name, value)
def __repr__(self):
values =
for i in self.__slots__:
# values = ', '.join(values)
return f'()'
class_attrs =
return type(name, (object,), class_attrs)
類裝飾器也是函式,與一般的裝飾器不同的是引數為類,用來審查,修改,甚至把被裝飾的類替換成其他類。讓我們寫乙個給類新增 cls_name 屬性的裝飾器吧:>>> dog = create_class('dog', 'name age owner')
>>> dog = dog('r', 2, 'assassin')
>>> dog
dog(name=r, age=2, owner=assassin)
利用類裝飾器可以對傳入的類做各種修改以達到使用需求。類裝飾器的缺點就是只對直接依附的類有效,這意味著子類有可能繼承也有可能不繼承被裝飾效果,這取決於裝飾器中所做的改動。def add_name(cls):
setattr(cls, 'cls_name', cls.__name__)
return cls
@add_name
class dog:
def __init__(self, name, age, owner):
self.name = name
self.age = age
self.owner = owner
除非開發框架,否則不要編寫元類——然而,為了尋找樂趣,或者練習相關概念,可以這麼做。一句話理解,元類就是用於構建類的類。——《流暢的python》
預設情況下,類都是 type 的例項,也就是說, type 是大多數內建類和自定義類的元類。 type 是乙個神奇的存在,它是自身的例項,而在 type 和 object 之間,type 是 object 的子類,object 是 type 的例項。
前面這些神奇的關係可以不用關注,但是編寫元類一定要明白的是:所有類都是 type 的例項,但只有元類同時還是 type 的子類,所以元類從 type 繼承了構建類的能力,這就是我們編寫元類的依據,具體來說,元類通過實現__init__
和__new__
方法來定製類,他們的區別如下:
所以,一般情況下我們想利用元類來對類進行審查,修改屬性時實現__init__
被稱為構造方法是從其他語言借鑑過來的術語,其實用於構建例項的是__new__
,這是個特殊處理的類方法,必須返回乙個例項,作為第乙個引數傳給__init__
方法,而__init__
禁止返回任何值,所以其實應該叫「初始化方法」。從__new__
到__init__
並不是必須的,因為__new__
方法非常強大,甚至可以返回其他例項,這時候不會呼叫__init__
方法。——《流暢的python》
__init__
方法即可,而如果需要根據已有類構造新類時就需要實現__new__
方法。
元類最常用在框架中,例如 orm 就會用到元類,當我們宣告乙個類並使用了框架提供的元類時,元類會做這些事:
orm 元類的編寫比較複雜,我以另外乙個例子說明元類的使用方法。在《python3網路爬蟲開發實戰》一書**池的例子中,我們需要實現乙個爬蟲類來爬取各個****的**,這個類的結構是這樣的:
我們在爬蟲類中定義了一系列針對各個**的爬取方法,並定義了乙個 get 方法來爬取指定的**,我們希望可以隨時新增可爬取的**,只需要新增以 crawl_ 開頭的方法。要實現這樣的功能,很明顯這樣是不夠的,因為我們不知道一共有哪些 crawl_ 開頭的爬取方法,如果再用另外的方式手動記錄又很麻煩,並且有忘記更新記錄的隱患存在。學習了元類後,我們可以很輕鬆的在爬蟲類中新增屬性來自動記錄其中的爬取方法,像下面這樣:class crawler():
def get_proxies(self, crawl_func):
'''執行指定方法來獲取**'''
pass
def crawl_1(self):
'''爬取**1的資料'''
pass
def crawl_2(self):
'''爬取**2的資料'''
pass
這樣後面工作的時候就可以呼叫class proxymetaclass(type):
'''元類,初始化類時記錄所有以crawl_開頭的方法'''
# 第乙個引數為元類的例項,後面三個與 type 用到的三個引數相同
def __init__(cls, name, bases, attrs):
count = 0
crawl_funcs =
for k, _ in attrs.items():
if 'crawl_' in k:
count += 1
# 新增屬性
cls.crawl_func_count = count
cls.crawl_funcs = crawl_funcs
# 爬蟲類,指定元類後會自動呼叫元類進行構建
class crawler(metaclass=proxymetaclass):
def get_proxies(self, crawl_func):
'''執行指定方法來獲取**'''
pass
def crawl_1(self):
'''爬取**1的資料'''
pass
def crawl_2(self):
'''爬取**2的資料'''
pass
crawler.crawl_funcs
獲取所有的 func 然後按個呼叫crawler.get_proxies(func)
進行爬取。
最後,元類功能強大但是難以掌握,類裝飾器能以更簡單的方式解決很多問題,比如上面這個需求,使用類裝飾器也可以很輕鬆的辦到(¬‿¬)。
python 元類程式設計
裝飾器任何時候你定義裝飾器的時候,都應該使用 functools 庫中的 wraps 裝飾器來註解底層包裝函式.因為乙個普通裝飾器作用在某個函式上時,這個函式的重要的元資訊比如名字 文件字串 註解和引數簽名都會丟失。但是 wraps不會。import time from functools impo...
python 元類程式設計
getattr 方法可用來檢查乙個類中是否有乙個屬性,比如 class user def init self,name self.name name def getattr self,item print not find attr def main user user dog user.age i...
python元類程式設計
當我們希望動態的產生類的時候,就需要用到元類程式設計的手段。要掌握此技術,需要明白以下幾個基本內容 metaclass type new call 在python 中,所有東西都是物件 object 包括python的類 class 也是乙個物件。檢視乙個物件的類,可以用物件的 class 屬性 c...