提高你的Python:具有型別的元類與動態類!
關鍵字 metaclass 與 type 都是很少用到(因此大多數人沒有很好理解)的 Python 構造。在本文裡,我們將探索 type() 不同的“型別”,以及 type 少有人知的使用如何與 metaclass 關聯。
你是我的型別嗎?
Type() 的第一個使用是最廣為人知的:確定一個物件的型別。這裡, Python 新手通常會打斷且說道,“不過我認為 Python 沒有型別!”相反, Python 裡每樣東西都要一個型別(甚至型別本身!)因為一切都是物件。讓我們看幾個例子:
>>> type ( 1 )
< class ' int '>
>>> type ( 'foo' )
< class ' str '>
>>> type ( 3.0 )
< class ' float '>
>>> type ( float )
< class ' type '>
type的型別
一切如預期,直到我們檢查 float 的型別。 <class ’type’> ?這是什麼?好吧,很奇怪,但讓我們繼續:
>>> class Foo ( object ):
... pass
...
>>> type (Foo)
< class ' type '>
啊!又一次 <class ‘type’> 。顯然,所有類本身的型別是 type (不管它們是內建的還是使用者定義的)。 Type 本身的型別是什麼呢?
>>> type ( type )
< class ' type '>
好吧,它在某處停止了。 Type 是所有型別的型別,包括自己。事實上, type 是一個 metaclass ,或者“構建類的一個事物”。類,比如 list() ,就像在 my_list = list() 裡,構建了該類的例項。同樣, metaclass 構建型別,就像下面的 Foo (譯註: Foo 類是一個使用者定義型別):
class Foo ( object ):
pass
運轉你自己的metaclass
就像普通的類, metaclass 可以是使用者定義的。要使用它,你將一個類的 __metaclass__ 屬性設定為你構建的 metaclass 。 Metaclass 可以是任意 callable ,只要它返回一個型別。通常,你將一個類的 __metaclass__ 賦給一個函式,在某處,該函式使用我們尚未討論的 type 的一個變體:用於建立類的三樣引數。
Type的黑暗面
如提到的,事實證明,在使用 3 個實參呼叫時, type 有一個完全獨立的用途。 Type(name, bases, dict) 以程式設計方式建立了一個新型別。如果我有以下程式碼:
class Foo ( object ):
pass
使用下面的程式碼我們可以實現完全相同的效果:
Foo = type ( 'Foo' , (), {})
現在 Foo 引用名為 Foo 的一個類,其基類是 object (使用 type 建立的類,如果沒有指定一個基類,會自動做成新形式的類)。
這都很好,但如果我們向向 Foo 新增成員函式會怎麼樣呢?很容易通過設定 Foo 的屬性實現這,就像這樣:
def always_false ( self ):
return False
Foo . always_false = always_false
我們可以使用下面的程式碼一口氣完成:
Foo = type ( 'Foo' , (), { 'always_false' : always_false})
當然, bases 引數是 Foo 的一組基類。我們把它留空,但建立一個從 Foo 派生的新類完全是合法的,再次使用 type 來建立它:
FooBar = type ( 'FooBar' , (Foo), {})
這什麼時候更有用?
一旦向別人解釋, type 與 metaclass 是接下來一個問題是“ OK ,那麼我什麼時候用它呢?”的其中一個話題。答案是,不經常。不過,有時使用 type 動態建立類是合適的解決方案。讓我們看一下一個例子。
Sandman 是我編寫的庫,用來自動生成一個 REST API 以及用於現有資料庫、基於 web 的管理介面的一個介面(無需要求任何樣板程式碼, boilerplate code )。大部分繁重的工作由 SQLAlchemy 完成,一個 ORM 框架。
使用 SQLAlchemy 註冊一個數據庫表僅有一個方式:建立一個描述該表的一個 Model 類(並非不像 Django 的模型)。為了讓 SQLAlchemy 認識一張表,必須以某種方式建立用於該表的一個類。因為 sandman 預先不知道資料庫的結構,它不能依賴預製的模型類來登錄檔。相反,它需要內省( introspect )資料庫,同時建立這些類。聽起來很熟悉?任何時候你動態地建立新類時, type 是正確的 / 僅有的選擇。
下面是 sandman 的相關程式碼:
if not current_app . endpoint_classes:
db . metadata . reflect(bind = db . engine)
for name in db . metadata . tables():
cls = type ( str (name), (sandman_model, db . Model),
{ '__tablename__' : name})
register(cls)
正如你看到的,如果使用者沒有為一張表手動建立一個模型類,它被自動建立,帶有一個設定為該表名字的 __tablename__ 屬性( SQLAlchemy 用來將表匹配到類)。
總結
在本文,我們討論了 type , metaclass 的兩個使用,以及何時要求 type 的另一個使用。雖然 metaclass 是一個有點令人混淆的概念,希望你現在有一個好的基礎,可以開展進一步的學習。
進群:960410445 即可獲取數十套PDF!