Python類元程式設計

2022-06-30 08:45:11 字數 4557 閱讀 8681

類元程式設計是指動態地建立或定製類,也就是在執行時根據不同的條件生成符合要求的類,一般來說,類元程式設計的主要方式有類工廠函式,類裝飾器和元類。

通常,我們都是使用 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 新建類並返回即可:

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)

利用這樣的類工廠函式可以很方便的建立出類似dog的簡單類,並且擁有了友好的字串表示形式:

>>> dog = create_class('dog', 'name age owner')

>>> dog = dog('r', 2, 'assassin')

>>> dog

dog(name=r, age=2, owner=assassin)

類裝飾器也是函式,與一般的裝飾器不同的是引數為類,用來審查,修改,甚至把被裝飾的類替換成其他類。讓我們寫乙個給類新增 cls_name 屬性的裝飾器吧:

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網路爬蟲開發實戰》一書**池的例子中,我們需要實現乙個爬蟲類來爬取各個****的**,這個類的結構是這樣的:

class crawler():

def get_proxies(self, crawl_func):

'''執行指定方法來獲取**'''

pass

def crawl_1(self):

'''爬取**1的資料'''

pass

def crawl_2(self):

'''爬取**2的資料'''

pass

我們在爬蟲類中定義了一系列針對各個**的爬取方法,並定義了乙個 get 方法來爬取指定的**,我們希望可以隨時新增可爬取的**,只需要新增以 crawl_ 開頭的方法。要實現這樣的功能,很明顯這樣是不夠的,因為我們不知道一共有哪些 crawl_ 開頭的爬取方法,如果再用另外的方式手動記錄又很麻煩,並且有忘記更新記錄的隱患存在。學習了元類後,我們可以很輕鬆的在爬蟲類中新增屬性來自動記錄其中的爬取方法,像下面這樣:

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...