Python 類與元類的深度挖掘 I【經(jīng)驗】
上一篇介紹了 Python 枚舉類型的標準庫,除了考慮到其實用性,還有一個重要的原因是其實現(xiàn)過程是一個非常好的學習、理解 Python 類與元類的例子。因此接下來兩篇就以此為例,深入挖掘 Python 中類與元類背后的機制。
翻開任何一本 Python 教程,你一定可以在某個位置看到下面這兩句話:
Python 中一切皆為對象(Everything in Python is an object);
Python 是一種面向對象編程(Object Oriented Programming, OOP)的語言。
雖然在上面兩句話的語境中,對象(Object)的含義可能稍有不同,但可以肯定的是對象在 Python 中具有非常重要的意義,也是我們接下來將要討論的所有內(nèi)容的基礎。那么,對象到底是什么?
對象(Object)
對象是 Python 中對數(shù)據(jù)的一種抽象,Python 程序中所有數(shù)據(jù)都是通過對象或對象之間的關系來表示的。[ref: Data Model]
港臺將 Object 翻譯為“物件”,可以將其看作是一個盛有數(shù)據(jù)的盒子,只不過除了純粹的數(shù)據(jù)之外還有其它有用的屬性信息,在 Python 中,所有的對象都具有id、type、value三個屬性:
+---------------+ | | | Python Object | | | +------+--------+ | ID | | +---------------+ | Type | | +---------------+ | Value| | +---------------+
其中 id 代表內(nèi)存地址,可以通過內(nèi)置函數(shù) id() 查看,而 type 表示對象的類別,不同的類別意味著該對象擁有的屬性和方法等,可以通過 type() 方法查看:
def who(obj): print(id(obj), type(obj)) who(1) who(None) who(who) 4515088368 4514812344 4542646064
對象作為 Python 中的基本單位,可以被創(chuàng)建、命名或刪除。Python 中一般不需要手動刪除對象,其垃圾回收機制會自動處理不再使用的對象,當然如果需要,也可以使用 del 語句刪除某個變量;所謂命名則是指給對象貼上一個名字標簽,方便使用,也就是聲明或賦值變量;接下來我們重點來看如何創(chuàng)建一個對象。對于一些 Python 內(nèi)置類型的對象,通常可以使用特定的語法生成,例如數(shù)字直接使用阿拉伯數(shù)字字面量,字符串使用引號 '',列表使用 [],字典使用 {} ,函數(shù)使用 def 語法等,這些對象的類型都是 Python 內(nèi)置的,那我們能不能創(chuàng)建其它類型的對象呢?
類與實例
既然說 Python 是面向對象編程語言,也就允許用戶自己創(chuàng)建對象,通常使用 class 語句,與其它對象不同的是,class 定義的對象(稱之為類)可以用于產(chǎn)生新的對象(稱之為實例):
class A: pass a = A() who(A) who(a) 140477703944616 4542635424
上面的例子中 A 是我們創(chuàng)建的一個新的類,而通過調(diào)用 A() 可以獲得一個 A 類型的實例對象,我們將其賦值為 a,也就是說我們成功創(chuàng)建了一個與所有內(nèi)置對象類型不同的對象 a,它的類型為 __main__.A!至此我們可以將 Python 中一切的對象分為兩種:
可以用來生成新對象的類,包括內(nèi)置的 int、str 以及自己定義的 A 等;
由類生成的實例對象,包括內(nèi)置類型的數(shù)字、字符串以及自己定義的類型為 __main__.A 的 a。
單純從概念上理解這兩種對象沒有任何問題,但是這里要討論的是在實踐中不得不考慮的一些細節(jié)性問題:
需要一些方便的機制來實現(xiàn)面向對象編程中的繼承、重載等特性;
需要一些固定的流程讓我們可以在生成實例化對象的過程中執(zhí)行一些特定的操作;
這兩個問題主要關于類的一些特殊的操作,也就是這一篇后面的主要內(nèi)容。如果再回顧一下開頭提到的兩句話,你可能會想到,既然類本身也是對象,那它們又是怎樣生成的?這就是后一篇將主要討論的問題:用于生成類對象的類,即元類(Metaclass)。
super, mro()
0x00 Python 之禪中提到的最后一條,命名空間(namespace)是個絕妙的理念,類或對象在 Python 中就承擔了一部分命名空間的作用。比如說某些特定的方法或屬性只有特定類型的對象才有,不同類型對象的屬性和方法盡管名字可能相同,但由于隸屬不同的命名空間,其值可能完全不同。在實現(xiàn)類的繼承與重載等特性時同樣需要考慮命名空間的問題,以枚舉類型的實現(xiàn)為例,我們需要保證枚舉對象的屬性名稱不能有重復,因此我們需要繼承內(nèi)置的 dict 類:
class _EnumDict(dict): def __init__(self): dict.__init__(self) self._member_names = [] def keys(self): keys = dict.keys(self) return list(filter(lambda k: k.isupper(), keys)) ed = _EnumDict() ed['RED'] = 1 ed['red'] = 2 print(ed, ed.keys()) {'RED': 1, 'red': 2} ['RED']
在上面的例子中 _EnumDict 重載同時調(diào)用了父類 dict 的一些方法,上面的寫法在語法上是沒有錯誤的,但是如果我們要改變 _EnumDict 的父類,不再是繼承自 dict,則必須手動修改所有方法中 dict.method(self) 的調(diào)用形式,這樣就不是一個好的實踐方案了。為了解決這一問題,Python 提供了一個內(nèi)置函數(shù) super():
print(super.__doc__) super() -> same as super(__class__, ) super(type) -> unbound super object super(type, obj) -> bound super object; requires isinstance(obj, type) super(type, type2) -> bound super object; requires issubclass(type2, type) Typical use to call a cooperative superclass method: class C(B): def meth(self, arg): super().meth(arg) This works for class methods too: class C(B): @classmethod def cmeth(cls, arg): super().cmeth(arg)
我最初只是把 super() 當做指向父類對象的指針,但實際上它可以提供更多功能:給定一個對象及其子類(這里對象要求至少是類對象,而子類可以是實例對象),從該對象父類的命名空間開始搜索對應的方法。
以下面的代碼為例:
class A(object): def method(self): who(self) print("A.method") class B(A): def method(self): who(self) print("B.method") class C(B): def method(self): who(self) print("C.method") class D(C): def __init__(self): super().method() super(__class__, self).method() super(C, self).method() # calling C's parent's method super(B, self).method() # calling B's parent's method super(B, C()).method() # calling B's parent's method with instance of C d = D() print("\nInstance of D:") who(d) 4542787992 C.method 4542787992 C.method 4542787992 B.method 4542787992 A.method 4542788048 A.method Instance of D: 4542787992
當然我們也可以在外部使用 super() 方法,只是不能再用缺省參數(shù)的形式,因為在外部的命名空間中不再存在 __class__ 和 self:
super(D, d).method() # calling D's parent's method with instance d 4542787992 C.method
上面的例子可以用下圖來描述:
+----------+ | A | +----------+ | method() <---------------+ super(B,self) +----------+ | | +----------+ +----------+ | B | | D | +----------+ super(C,self) +----------+ | method() <---------------+ method() | +----------+ +----------+ | +----------+ | | C | | +----------+ | super(D,self) | method() <---------------+ +----------+
可以認為 super() 方法通過向父類方向回溯給我們找到了變量搜尋的起點,但是這個回溯的順序是如何確定的呢?上面的例子中繼承關系是 object->A->B->C->D 的順序,如果是比較復雜的繼承關系呢?
class A(object): pass class B(A): def method(self): print("B's method") class C(A): def method(self): print("C's method") class D(B, C): def __init__(self): super().method() class E(C, B): def __init__(self): super().method() d = D() e = E() B's method C's method
Python 中提供了一個類方法 mro() 可以指定搜尋的順序,mro 是Method Resolution Order 的縮寫,它是類方法而不是實例方法,可以通過重載 mro() 方法改變繼承中的方法解析順序,但這需要在元類中完成,在這里只看一下其結果:
D.mro() [__main__.D, __main__.B, __main__.C, __main__.A, object] E.mro() [__main__.E, __main__.C, __main__.B, __main__.A, object] super() 方法就是沿著 mro() 給出的順序向上尋找起點的: super(D, d).method() super(E, e).method() B's method C's method super(C, e).method() super(B, d).method() B's method C's method
相關文章
Python(Django)項目與Apache的管理交互的方法
這篇文章主要介紹了Python(Django)項目與Apache的管理交互的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05pytorch 權重weight 與 梯度grad 可視化操作
這篇文章主要介紹了pytorch 權重weight 與 梯度grad 可視化操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06使用python實現(xiàn)深度優(yōu)先遍歷搜索(DFS)的示例代碼
深度優(yōu)先搜索算法(Depth-First-Search,DFS)是一種用于遍歷或搜索樹或圖的算法,沿著樹的深度遍歷樹的節(jié)點,盡可能深的搜索樹的分支,本文給大家介紹了如何基于python實現(xiàn)深度優(yōu)先遍歷搜索(DFS),需要的朋友可以參考下2024-01-01深入理解Tensorflow中的masking和padding
TensorFlow 是一個用于人工智能的開源神器,這篇文章主要介紹了Tensorflow中的masking和padding的相關知識,通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02