Following system colour scheme - Python 增強提案 Selected dark colour scheme - Python 增強提案 Selected light colour scheme - Python 增強提案

Python 增強提案 (Python Enhancement Proposals)

PEP 252 – 讓型別看起來更像類別

作者:
Guido van Rossum <guido at python.org>
狀態:
最終 (Final)
類型:
標準軌跡 (Standards Track)
建立日期:
2001 年 4 月 19 日
Python 版本:
2.2
公告歷史:


目錄

摘要

本 PEP 提議變更型別的內省 API,使其看起來更像類別,並使它們的實例看起來更像類別實例。例如,對於大多數內建型別,type(x) 將等同於 x.__class__。當 C 為 x.__class__ 時,x.meth(a) 通常等同於 C.meth(x, a),且 C.__dict__ 包含 x 的方法及其他屬性。

本 PEP 也引入了一種定義屬性的新方法,即使用屬性描述器(簡稱描述器)。描述器統一並概括了幾種描述屬性的常見機制:描述器可以描述一個方法、物件結構中的型別化欄位,或是由 getter 和 setter 函式所呈現的通用屬性。

基於通用的描述器 API,本 PEP 也引入了宣告類別方法與靜態方法的方式。

[編者註:本 PEP 描述的構想已併入 Python。此 PEP 已無法準確描述目前的實作內容。]

簡介

類別與型別之間的差異是 Python 最古老的語言瑕疵之一。例如,你無法直接繼承字典型別,且用來尋找物件具備哪些方法與實例變數的內省介面,在型別與類別之間也是不同的。

修復類別與型別的分裂是一項浩大工程,因為這會影響 Python 實作的許多層面。本 PEP 僅關注於使型別的內省 API 與類別的看起來相同。其他 PEP 將會提議讓類別看起來更像型別,以及從內建型別繼承;這些主題不在本 PEP 的討論範圍內。

內省 API (Introspection APIs)

內省關注的是找出物件擁有什麼屬性。Python 非常通用的 getattr/setattr API 使得我們無法保證總是有辦法取得特定物件所支援的所有屬性清單,但在實務上出現了兩套慣例,幾乎適用於所有物件。我將稱它們為「基於類別的內省 API」與「基於型別的內省 API」;簡稱為類別 API 與型別 API。

基於類別的內省 API 主要用於類別實例;它也被 Jim Fulton 的 ExtensionClasses 所使用。它假設物件 x 的所有資料屬性都儲存在字典 x.__dict__ 中,且所有方法與類別變數都可以透過檢查 x 的類別(寫作 x.__class__)找到。類別具有 __dict__ 屬性,它會產生一個字典,其中包含由該類別自身定義的方法與類別變數,以及一個 __bases__ 屬性,這是一個必須遞迴檢查的基底類別元組。這裡的一些假設是:

  • 定義在實例字典中的屬性會覆寫定義在物件類別中的屬性;
  • 定義在衍生類別中的屬性會覆寫定義在基底類別中的屬性;
  • 位於較早基底類別(意即在 __bases__ 中出現較早)的屬性會覆寫位於較晚基底類別的屬性。

(後兩條規則合起來通常被總結為屬性搜尋的「由左至右、深度優先」規則。這是傳統的 Python 屬性查詢規則。請注意,PEP 253 將提議變更屬性查詢順序,若該 PEP 被接受,本 PEP 也將隨之調整。)

基於型別的內省 API 受到大多數內建物件以某種形式支援。它使用兩個特殊屬性:__members____methods__。若 __methods__ 屬性存在,它是一個該物件支援的方法名稱清單;若 __members__ 屬性存在,它是一個該物件支援的資料屬性名稱清單。

型別 API 有時會與一個運作方式與實例相同的 __dict__ 結合使用(例如 Python 2.1 中的函式物件,f.__dict__ 包含 f 的動態屬性,而 f.__members__ 列出 f 的靜態定義屬性名稱)。

必須謹慎行事:有些物件不會將其「內在」屬性(如 __dict____doc__)列在 __members__ 中,而有些則會;有時屬性名稱會同時出現在 __members____methods__ 中,以及作為 __dict__ 的鍵,在這種情況下,沒人知道是否使用了 __dict__ 中的值。

型別 API 從未被仔細規範。它是 Python 傳承的一部分,大多數第三方擴充套件之所以支援它,是因為它們遵循了支援它的範例。此外,任何在其 tp_getattr 處理程式中使用 Py_FindMethod() 和/或 PyMember_Get() 的型別都支援它,因為這兩個函式分別對屬性名稱 __methods____members__ 進行了特殊處理。

Jim Fulton 的 ExtensionClasses 忽略了型別 API,轉而模擬功能更強大的類別 API。在本 PEP 中,我提議逐步淘汰型別 API,轉而對所有型別支援類別 API。

支持類別 API 的一個論點是,它不需要你建立一個實例就能得知型別支援哪些屬性;這對於文件處理器很有用。例如,socket 模組導出了 SocketType 物件,但目前這無法告訴我們 socket 物件定義了哪些方法。使用類別 API,SocketType 將精確顯示 socket 物件的方法為何,甚至可以在不建立 socket 的情況下提取其 docstring。(由於這是 C 擴充模組,源碼掃描式的 docstring 提取在此情況下不可行。)

基於類別的內省 API 規範

物件可能有兩種屬性:靜態與動態。靜態屬性的名稱(有時還有其他屬性)可以透過檢查物件的型別或類別來得知,這些可透過 obj.__class__type(obj) 存取。(我將型別與類別混用;一個雖笨拙但具描述性且兩者皆適用的術語是「元物件 (meta-object)」)。

(XXX 靜態與動態在此並非絕佳術語,因為「靜態」屬性實際上可能運作得非常動態,且它們與 C++ 或 Java 中的靜態類別成員無關。Barry 建議使用「不可變 (immutable)」與「可變 (mutable)」,但這些詞彙在略有不同的上下文中已經有了精確且不同的含義,所以我認為那樣依然會造成混淆。)

動態屬性的範例有類別實例的實例變數、模組屬性等。靜態屬性的範例有清單與字典等內建物件的方法,以及框架 (frame) 與程式碼 (code) 物件的屬性(f.f_code, c.co_filename 等)。當一個具有動態屬性的物件透過其 __dict__ 屬性暴露這些屬性時,__dict__ 本身就是一個靜態屬性。

動態屬性的名稱與值通常儲存在一個字典中,且此字典通常可作為 obj.__dict__ 存取。本規範的其餘部分更多地關注於發現靜態屬性的名稱與屬性,而非動態屬性;後者可以透過檢查 obj.__dict__ 輕易發現。

在下方的討論中,我區分了兩種物件:一般物件(如清單、整數、函式)與元物件。型別與類別是元物件。元物件也是一般物件,但我們對它們感興趣主要是因為它們被一般物件的 __class__ 屬性(或是其他元物件的 __bases__ 屬性)所參照。

類別內省 API 由以下元素組成:

  • 一般物件上的 __class____dict__ 屬性;
  • 元物件上的 __bases____dict__ 屬性;
  • 優先權規則;
  • 屬性描述器。

這些不僅能告訴我們元物件定義的所有屬性,還能幫助我們計算給定物件之特定屬性的值。

  1. 一般物件上的 __dict__ 屬性

    一般物件可能會有一個 __dict__ 屬性。若有,這應該是一個支援至少 __getitem__()keys()has_key() 的映射(不一定是字典)。這提供了該物件的動態屬性。映射中的鍵提供屬性名稱,對應的值提供其數值。

    通常,具有給定名稱之屬性的值,與該名稱作為 __dict__ 中的鍵所對應的值為同一個物件。換句話說,obj.__dict__['spam'] 就是 obj.spam。(但請參閱下方的優先權規則;具有相同名稱的靜態屬性可能會覆寫字典項目。)

  2. 一般物件上的 __class__ 屬性

    一般物件通常有一個 __class__ 屬性。若有,它會參照一個元物件。元物件可以為該一般物件定義靜態屬性。這通常是透過以下機制完成:

  3. 元物件上的 __dict__ 屬性

    元物件可能有一個 __dict__ 屬性,其形式與一般物件的 __dict__ 相同(是一個映射但不必是字典)。若有,元物件 __dict__ 中的鍵就是對應一般物件的靜態屬性名稱。其值為屬性描述器;我們稍後會解釋這些。未綁定方法 (unbound method) 是屬性描述器的一種特殊情況。

    由於元物件也是一般物件,元物件 __dict__ 中的項目對應於元物件的屬性;然而,可能會進行某些轉換,且基底(參見下文)可能會定義額外的動態屬性。換句話說,mobj.spam 不總是等於 mobj.__dict__['spam']。(此規則包含一個漏洞,因為對於類別而言,若 C.__dict__['spam'] 是一個函式,則 C.spam 是一個未綁定方法物件。)

  4. 元物件上的 __bases__ 屬性

    元物件可能有一個 __bases__ 屬性。若有,這應該是其他元物件(基底)的序列(不必是元組)。缺失的 __bases__ 等同於空序列。由 __bases__ 屬性定義的元物件關係之間絕對不能有循環;換句話說,__bases__ 屬性定義了一個有向無環圖 (DAG),弧線從衍生元物件指向其基底元物件。(這不一定是樹狀結構,因為多個類別可以有相同的基底類別。)繼承圖中元物件的 __dict__ 屬性為該一般物件(其 __class__ 屬性指向繼承樹的根,這並非繼承層級的根,反而更像是相反,在典型繪製繼承樹時位於底部)提供屬性描述器。描述器會先在根元物件的字典中搜尋,然後在其實際基底中搜尋,根據優先權規則(參見下一段)。

  5. 優先權規則

    當給定一般物件的繼承圖中有兩個元物件同時定義了相同名稱的屬性描述器時,搜尋順序取決於該元物件。這允許不同的元物件定義不同的搜尋順序。特別是,傳統類別使用舊的「由左至右、深度優先」規則,而新式類別使用更進階的規則(參見 PEP 253 中的方法解析順序章節)。

    當一個動態屬性(定義在一般物件的 __dict__ 中)與一個靜態屬性(定義在以該物件 __class__ 為根的繼承圖中的某個元物件中)名稱相同時,若該靜態屬性是一個定義了 __set__ 方法的描述器(見下文),則靜態屬性優先;否則(若無 __set__ 方法),則動態屬性優先。換句話說,對於資料屬性(那些具有 __set__ 方法的),靜態定義會覆寫動態定義;但對於其他屬性,則是動態覆寫靜態。

    基本原理:我們無法設定簡單的規則如「靜態覆寫動態」或「動態覆寫靜態」,因為有些靜態屬性確實會覆寫動態屬性;例如,實例 __dict__ 中的鍵 '__class__' 會被忽略,改為使用靜態定義的 __class__ 指標;但另一方面,inst.__dict__ 中的大多數鍵都會覆寫 inst.__class__ 中定義的屬性。描述器上是否存在 __set__ 方法,代表這是否為「資料描述器」。(即使是唯讀資料描述器也有 __set__ 方法:它總是會引發例外。) 描述器上若缺少 __set__ 方法,代表描述器對攔截賦值不感興趣,此時適用傳統規則:與方法名稱相同的實例變數會隱藏該方法,直到被刪除為止。

  6. 屬性描述器

    這部分變得很有趣——也變得混亂。屬性描述器(簡稱描述器)儲存在元物件的 __dict__(或其祖先之一的 __dict__)中,有兩種用途:描述器可用於取得或設定(一般、非元)物件上的對應屬性值,並且具有一個額外的介面,用於文件與內省目的。

    Python 在設計描述器介面方面幾乎沒有先例,無論是在取得/設定值,還是描述屬性方面,除了一些瑣碎的屬性(可以合理地假設 __name____doc__ 應該是該屬性的名稱與 docstring)。我將在下方提議這樣的 API。

    如果元物件 __dict__ 中找到的物件不是屬性描述器,向後相容性決定了某些最小語義。這基本上意味著如果它是 Python 函式或未綁定方法,該屬性就是一個方法;否則,它就是動態資料屬性的預設值。向後相容性也規定(在沒有 __setattr__ 方法的情況下)可以對應於方法的屬性進行賦值,並且這會建立一個針對此特定實例陰影該方法的資料屬性。然而,這些語義僅為了與傳統類別的向後相容性而要求。

內省 API 是一個唯讀 API。我們沒有定義對任何特殊屬性(__dict____class____bases__)賦值的效果,也不定義對 __dict__ 中的項目賦值的效果。通常,這類賦值應被視為禁區。未來的 PEP 可能會為某些此類賦值定義一些語義。(特別是因為目前實例支援對 __class____dict__ 賦值,而類別支援對 __bases____dict__ 賦值。)

屬性描述器 (Attribute Descriptor) API 規範

屬性描述器可以有以下屬性。在範例中,x 是一個物件,C 是 x.__class__x.meth() 是一個方法,而 x.ivar 是一個資料屬性或實例變數。所有屬性皆為選填——特定屬性可能存在也可能不存在於給定的描述器上。屬性缺失意味著對應資訊不可用,或對應功能未實作。

  • __name__:屬性名稱。由於別名與重新命名,該屬性可能(額外或專門)以不同的名稱為人所知,但這是它誕生時的名字。範例:C.meth.__name__ == 'meth'
  • __doc__:屬性的文件字串。這可能是 None。
  • __objclass__:宣告此屬性的類別。描述器僅適用於身為該類別實例的物件(這包含其子類別的實例)。範例:C.meth.__objclass__ is C
  • __get__():一個可用一或兩個參數呼叫的函式,從物件中擷取屬性值。這也稱為「綁定 (binding)」操作,因為它在方法描述器的情況下可能會回傳一個「綁定方法 (bound method)」物件。第一個參數 X 是必須從中擷取屬性或必須綁定到的物件。當 X 為 None 時,選擇性的第二個參數 T 應為元物件,且綁定操作可能會回傳一個僅限於 T 實例的未綁定方法。當同時指定 X 與 T 時,X 應為 T 的實例。綁定操作回傳的內容取決於描述器的語義;例如,靜態方法與類別方法(見下文)會忽略實例,改為綁定到型別。
  • __set__():一個具有兩個參數的函式,用於設定物件上的屬性值。若該屬性為唯讀,此方法可能會引發 TypeError 或 AttributeError 例外(兩者皆允許,因為歷史上對於未定義或不可設定的屬性,兩者皆有出現)。範例:C.ivar.set(x, y) ~~ x.ivar = y

靜態方法與類別方法

描述器 API 使得加入靜態方法與類別方法成為可能。靜態方法很容易描述:它們的表現幾乎與 C++ 或 Java 中的靜態方法相同。這是一個範例:

class C:

    def foo(x, y):
        print "staticmethod", x, y
    foo = staticmethod(foo)

C.foo(1, 2)
c = C()
c.foo(1, 2)

呼叫 C.foo(1, 2)c.foo(1, 2) 都會呼叫帶有兩個參數的 foo(),並列印「staticmethod 1 2」。在 foo() 的定義中沒有宣告「self」,且呼叫時也不需要實例。

類別敘述中的「foo = staticmethod(foo)」這一行是關鍵元素:這讓 foo() 成為靜態方法。內建的 staticmethod() 將其函式參數封裝在一種特殊的描述器中,其 __get__() 方法回傳原始函式且不做變動。若沒有這樣做,標準函式物件的 __get__() 方法會為 ‘c.foo’ 建立一個綁定方法物件,並為 ‘C.foo’ 建立一個未綁定方法物件。

(XXX Barry 建議使用「sharedmethod」而不是「staticmethod」,因為 static 這個詞已經在太多方面被過度使用。但我不太確定 shared 是否傳達了正確的含義。)

類別方法使用類似的模式來宣告方法,這些方法會接收一個隱含的第一參數,即呼叫它們時的類別。這在 C++ 或 Java 中沒有對應項目,且與 Smalltalk 中的類別方法不完全相同,但可能達到類似的目的。根據 Armin Rigo 的說法,它們類似於 Borland Pascal 方言 Delphi 中的「虛擬類別方法 (virtual class methods)」。(Python 也有真正的元類別,或許定義在元類別中的方法更有權利使用「類別方法」這個名稱;但我預期大多數程式設計師不會使用元類別。) 這是一個範例:

class C:

    def foo(cls, y):
        print "classmethod", cls, y
    foo = classmethod(foo)

C.foo(1)
c = C()
c.foo(1)

呼叫 C.foo(1)c.foo(1) 最後都會呼叫帶有兩個參數的 foo(),並列印「classmethod __main__.C 1」。foo() 的第一個參數是隱含的,它就是該類別,即使該方法是透過實例呼叫。現在我們繼續這個範例:

class D(C):
    pass

D.foo(1)
d = D()
d.foo(1)

兩次呼叫都列印「classmethod __main__.D 1」;換句話說,作為 foo() 第一個參數傳入的類別,是呼叫時涉及的類別,而不是定義 foo() 時涉及的類別。

但請注意這一點:

class E(C):
    def foo(cls, y): # override C.foo
        print "E.foo() called"
        C.foo(y)
    foo = classmethod(foo)

E.foo(1)
e = E()
e.foo(1)

在此範例中,從 E.foo() 呼叫 C.foo() 將會把類別 C 視為其第一個參數,而不是類別 E。這是預期之中的,因為呼叫時指定了類別 C。但這強調了這些類別方法與定義在元類別中方法的差異,在元類別中,對元方法的向上呼叫 (upcall) 會將目標類別作為明確的第一個參數傳遞。(如果你不懂這一點,別擔心,你並不孤單。) 請注意,呼叫 cls.foo(y) 會是一個錯誤——它會導致無限遞迴。也請注意,你無法為類別方法指定明確的 ‘cls’ 參數。如果你想要這樣做(例如 PEP 253 中的 __new__ 方法有此要求),請改用靜態方法並將類別作為其明確的第一參數。

C API

XXX 以下是我寫給不同讀者群的草稿,文字非常粗糙;我必須重新編輯它。XXX 它也沒有為 C API 提供足夠細節。

內建型別可以透過兩種方式宣告特殊資料屬性:使用 struct memberlist (定義在 structmember.h 中) 或 struct getsetlist (定義在 descrobject.h 中)。struct memberlist 是一種舊機制,但有了新用途:每個屬性都有一個描述器記錄,包含其名稱、給定其型別的 enum (支援各種 C 型別以及 PyObject *)、與實例開頭的偏移量,以及唯讀旗標。

struct getsetlist 機制是新的,旨在處理不適合上述模式的情況,因為它們要麼需要額外的檢查,要麼是純粹的計算屬性。這裡的每個屬性都有一個名稱、一個 getter C 函式指標、一個 setter C 函式指標,以及一個上下文指標。函式指標是選填的,例如將 setter 函式指標設定為 NULL 即可製作唯讀屬性。上下文指標旨在將輔助資訊傳遞給通用 getter/setter 函式,但我還沒發現有此需求。

請注意,還有一個類似的機制用來宣告內建方法:這些是 PyMethodDef 結構,其中包含名稱與 C 函式指標 (以及一些呼叫慣例的旗標)。

傳統上,內建型別必須定義自己的 tp_getattrotp_setattro 插槽函式,才能讓這些屬性定義運作 (PyMethodDef 與 struct memberlist 非常古老)。有一些方便的函式,它們接受一個 PyMethodDef 或 memberlist 結構陣列、一個物件與一個屬性名稱,如果能在清單中找到則回傳或設定該屬性,如果沒找到則引發例外。但這些便利函式必須由特定型別的 tp_getattrotp_setattro 方法明確呼叫,且它們使用 strcmp() 對陣列進行線性搜尋,以找到描述所請求屬性的陣列元素。

我現在有一個全新的通用機制,大大改善了這種情況。

  • PyMethodDef、memberlist、getsetlist 結構陣列的指標現在是新型別物件的一部分 (tp_methodstp_memberstp_getset)。
  • 在型別初始化時 (在 PyType_InitDict() 中),針對這三個陣列中的每個條目,都會建立一個描述器物件,並放置在屬於該型別的字典 (tp_dict) 中。
  • 描述器是非常精簡的物件,主要指向對應的結構。一個實作細節是所有描述器共用相同的物件型別,而一個判別欄位會告訴我們它是哪種描述器 (方法、成員或 getset)。
  • PEP 252 中所述,描述器有一個 get() 方法,該方法接受一個物件參數並回傳該物件的屬性;可寫入屬性的描述器還有一個 set() 方法,接受一個物件與一個值,並設定該物件的屬性。請注意,get() 物件也作為方法的 bind() 操作,將未綁定方法實作綁定到該物件上。
  • 幾乎所有內建物件現在都不再提供自己的 tp_getattro 與 tp_setattro 實作,而是將 PyObject_GenericGetAttr 以及 (若有任何可寫入屬性) PyObject_GenericSetAttr 放入其 tp_getattrotp_setattro 插槽中。(或者,它們可以將這些保留為 NULL,並從預設基底物件繼承,前提是在第一個實例建立之前,為該型別明確呼叫了 PyType_InitDict()。)
  • 在最簡單的情況下,PyObject_GenericGetAttr() 只做一次字典查詢:它在型別的字典 (obj->ob_type->tp_dict) 中尋找屬性名稱。成功後,有兩種可能性:描述器有 get 方法,或沒有。為了速度,get 與 set 方法是型別插槽:tp_descr_gettp_descr_set。若 tp_descr_get 插槽非 NULL,則呼叫它,僅傳遞該物件作為參數,此呼叫的回傳值即為 getattr 操作的結果。若 tp_descr_get 插槽為 NULL,則作為後備方案,會直接回傳描述器本身 (比較那些不是方法而是簡單值的類別屬性)。
  • PyObject_GenericSetAttr() 運作方式非常相似,但使用 tp_descr_set 插槽,並以該物件與新屬性值呼叫它;若 tp_descr_set 插槽為 NULL,則引發 AttributeError
  • 但現在來看看一個更複雜的情況。上述方法適用於大多數內建物件,如清單、字串、數字。然而,某些物件型別在每個實例中都有一個可以儲存任意屬性的字典。事實上,當你使用 class 敘述來繼承現有的內建型別時,你會自動獲得這樣一個字典(除非你使用另一個進階功能 __slots__ 明確關閉它)。讓我們稱之為「實例字典 (instance dict)」,以區別於型別字典 (type dict)。
  • 在更複雜的情況下,實例字典中儲存的名稱與型別字典中儲存的名稱會發生衝突。如果兩個字典都有一個帶有相同鍵的項目,我們應該回傳哪一個?以傳統 Python 為指引,我發現了相互衝突的規則:對於類別實例,實例字典會覆寫類別字典,除了特殊屬性(如 __dict____class__),它們比實例字典具有優先權。
  • 我透過以下規則集解決了這個問題,並實作在 PyObject_GenericGetAttr() 中:
    1. 查看型別字典。如果找到一個資料描述器,使用其 get() 方法產生結果。這處理了像 __dict____class__ 這樣的特殊屬性。
    2. 查看實例字典。如果找到任何東西,就是它了。(這處理了通常情況下實例字典會覆寫類別字典的需求。)
    3. 再次查看型別字典 (實際上當然是使用步驟 1 中儲存的結果)。如果找到一個描述器,使用其 get() 方法;如果找到其他東西,就是它了;如果都沒有,引發 AttributeError

    這需要將描述器分類為「資料」與「非資料」描述器。目前的實作相當合理地將成員與 getset 描述器歸類為資料 (即使它們是唯讀的!),並將方法描述器歸類為非資料。非描述器 (如函式指標或純值) 也被歸類為非資料 (!)。

  • 此方案有一個缺點:在我假設最常見的情況下,即參照儲存在實例字典中的實例變數,它會進行兩次字典查詢,而傳統方案對以兩個底線開頭的屬性進行快速測試,並僅進行一次字典查詢。(雖然該實作悲慘地結構化為 instance_getattr() 呼叫 instance_getattr1() 呼叫 instance_getattr2(),最後呼叫 PyDict_GetItem(),且底線測試呼叫的是 PyString_AsString() 而非內聯處理。我懷疑如果我們不打算將其全部拆除,對此進行極致優化以加速 Python 2.2 是否會是個好主意。 :-)
  • 基準測試驗證,事實上這與傳統的實例變數查詢一樣快,所以我不再擔心了。
  • 動態型別的修改:步驟 1 與 3 會查看該型別及其所有基底類別的字典 (當然是在 MRO 順序中)。

討論

XXX

範例

讓我們看看清單。在傳統 Python 中,清單的方法名稱可作為清單物件的 __methods__ 屬性使用:

>>> [].__methods__
['append', 'count', 'extend', 'index', 'insert', 'pop',
'remove', 'reverse', 'sort']
>>>

在新提議下,__methods__ 屬性不再存在:

>>> [].__methods__
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: 'list' object has no attribute '__methods__'
>>>

相反地,你可以從清單型別中獲得相同的資訊:

>>> T = [].__class__
>>> T
<type 'list'>
>>> dir(T)                # like T.__dict__.keys(), but sorted
['__add__', '__class__', '__contains__', '__eq__', '__ge__',
'__getattr__', '__getitem__', '__getslice__', '__gt__',
'__iadd__', '__imul__', '__init__', '__le__', '__len__',
'__lt__', '__mul__', '__ne__', '__new__', '__radd__',
'__repr__', '__rmul__', '__setitem__', '__setslice__', 'append',
'count', 'extend', 'index', 'insert', 'pop', 'remove',
'reverse', 'sort']
>>>

新的內省 API 提供了比舊版更多的資訊:除了常規方法外,它還顯示了通常透過特殊符號呼叫的方法,例如 __iadd__ (+=)、__len__ (len)、__ne__ (!=)。你可以直接從此清單中呼叫任何方法:

>>> a = ['tic', 'tac']
>>> T.__len__(a)          # same as len(a)
2
>>> T.append(a, 'toe')    # same as a.append('toe')
>>> a
['tic', 'tac', 'toe']
>>>

這就像使用者定義的類別一樣。

請注意清單中一個熟悉卻令人驚訝的名稱:__init__。這是 PEP 253 的領域。

回溯相容性

XXX

警告與錯誤

XXX

實作

本 PEP 的部分實作可從 CVS 以名為 “descr-branch” 的分支取得。若要嘗試此實作,請根據 http://sourceforge.net/cvs/?group_id=5470 的說明從 CVS 簽出 Python,但在 cvs checkout 指令中加入參數 “-r descr-branch”。(你也可以從現有的簽出開始,執行 “cvs update -r descr-branch”。) 有關此處描述功能的範例,請參閱檔案 Lib/test/test_descr.py。

注意:此分支中的程式碼遠超出了本 PEP 的範圍;它同時也是 PEP 253 (子型別內建型別) 的實驗區。

參考文獻

XXX


來源:https://github.com/python/peps/blob/main/peps/pep-0252.rst

最後修改:2025-02-01 08:55:40 GMT