本來用sphinx+rst輸出html,但貼上來發(fā)現(xiàn)效果不行,因此直接貼上rst的。
Section 5.1 Classes and Instances
+++++++++++++++++++++++++++++++++
如果你已經(jīng)熟悉面向?qū)ο缶幊蹋裨谄渌Z言,C++或Java,這樣你可能有一個很好的理解,像類和實例:一個類,就是一個用戶定義的類型,你可以將它實例化得到實例對象,而它們就是這個類型的。Python支持這些概念,通過它的類和各個實例對象。
5.1.1 Python Classes
--------------------
類就是有一些特性的python對象:
* 你可以調(diào)用一個類,好像它就是一個函數(shù)。這個調(diào)用返回了另一個對象,這就是一個類的實例;而類也就是這個實例的類型。
* 一個類的屬性可以任意的綁定和引用。
* 類屬性的值可以是描述符(包括函數(shù)),或者是普通的數(shù)據(jù)對象。
* 類屬性綁定了函數(shù),就是我們所知的類的方法。
* 一個方法,可以有一個特殊的python定義的名字,它是以兩個下劃線起始,而又以兩個下劃線結(jié)尾。Python會隱式地調(diào)用這些特殊的方法,如果一個類提供了它們,并且當各個操作發(fā)生在那個類的實例上。
* 一個類可以從其他類中繼承,這就意味著它如果在本身中沒有找到一個屬性,那么它就會委托給其他類,讓它們在它們的類中搜索。
一個類的實例,是一個python對象,有任意命名了的屬性,而你可以綁定和引用。一個實例對象隱式地將在實例本身中未搜尋到的屬性委托給它的類來搜索。而這個類,如果可以,又會依次的搜索它所繼承的類。
在python中,類是對象,而且可以像其他對象那樣使用。因此,你可以將一個類作為參數(shù),傳遞給一個函數(shù)。同樣的,一個函數(shù)可以返回一個類。一個類,就像其他的對象,可以綁定一個變量(全局或局部),一個容器中的元素,或者是一個對象的屬性。類也可以成為一個字典的鍵值。事實上,類是一個普通的對象,這也就是常說的類是一級對象。
5.1.2 The class Statement
-------------------------
``class`` 語句是最常見的方式,來創(chuàng)建一個類對象。 ``class`` 是一個單子句符合語句,遵循下列的語法:
::
class classname(base-classes):
statement(s)
``classname`` 是一個標識符。它是一個變量,可以綁定到類對象,在 ``class`` 語句完成執(zhí)行后。
``base-classes`` 是一系列以逗號分隔,而值都必須是類對象的表達式。這些類在不同的編程語言中有著不同的名字;這完全看你喜歡,可以是基類,超類,或者是被創(chuàng)建類的父類。而被創(chuàng)建的類,可以說是繼承,派生,擴展,或是子類。這都完全看你所熟悉的語言是怎么稱呼它們的。這個類也可以說是一個直接子類,或者是它基類的后代。
語法上, ``base-classes`` 是可選的:為了表示你創(chuàng)建的類是沒有基類的,你可以忽略 ``base-classes`` (而且還有它兩邊的括號也要忽略),直接將冒號放在類名字后面(在python 2.5中,你也可以使用這對空括號,其意思是一樣的)。但是,一個沒有基類的類,由于向后兼容的問題,所以是一個 **舊式** 的類(除非你定義了 ``__metaclass__`` 屬性)。為了創(chuàng)建 **新式** 的類,而又沒有“真正”的基類,那么這樣編寫代碼: ``class C(object):`` ;因為每個類型都把內(nèi)置對象歸入子類,為了區(qū)分是新式的類還是舊式的類,我們就用 ``object`` 來加以指明。如果你的類的祖先都是舊式的類,而又沒有定義 ``__metaclass__`` 屬性,那么它就是舊式的;否則,有基類的類總是新式的類(就算有些基類是新式的,有些是舊式的)。
在各個類中的子類關(guān)系,是可傳遞的:如果 ``C1`` 是 ``C2`` 的子類,而 ``C2`` 又是 ``C3`` 的子類,那么, ``C1`` 就是 ``C3`` 的子類。內(nèi)置函數(shù) ``issubclass(C1,C2)`` 接受兩個是類對象的參數(shù):它將會返回 ``True`` 如果 ``C1`` 是 ``C2`` 的子類;否則將返回 ``False`` 。任何一個類將被認為是自己的子類;所以, ``issubclass(C,C)`` 將會返回 ``True`` 。關(guān)于基類如何影響這個類的方式可以參考“繼承”。
而在 ``class`` 語句后面的不為空的語句,則被稱為類體。一個類體在 ``class`` 語句被執(zhí)行時也就被作為其一部分被執(zhí)行了。直到類體結(jié)束執(zhí)行,一個新的類對象還不存在,并且 ``classname`` 標識符也并未綁定(或重綁定)。在“如何用元類構(gòu)造類中”提供了更詳細的信息。
最后,注意, ``class`` 語句并不是立即創(chuàng)建任何新類的實例,而是定義了一系列的屬性的集合,而這個集合又被你后來創(chuàng)建的實例所共享。
5.1.3 The Class Body
--------------------
一個類的主體,是你指定類屬性的地方;這些屬性可以是描述符對象(包括函數(shù)),或者是任意類型的普通數(shù)據(jù)對象(一個類的屬性可以是另一個類,舉個例子,你可以有一個嵌套的 ``class`` 語句在另一個 ``class`` 語句中。)
5.1.3.1 Attributes of class objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
你可以指定一個類的屬性,通過綁定一個值給類體中的標識符。舉個例子:
::
class C1(object):
x = 23
print C1.x
#prints:23
類對象 ``C1`` 有一個屬性,叫做 ``x`` ,它綁定了一個值 ``23`` ,而 ``C1.x`` 則指向這個屬性。
你可以在類體外綁定或解除屬性。舉個例子:
::
class C2(object):
pass
C2.x = 23
print C2.x
#prints: 23
但是,你的程序如果把綁定屬性之類的東西都放入類體中,這樣可讀性可能高一點。任何類屬性,都是隱式的被全部的實例共享,一旦實例被創(chuàng)建。這個我們后面很快就會討論。
使用了 ``class`` 語句后,將會隱式的設(shè)置一些類屬性。屬性 ``__name__`` 是 ``classname`` 標識符的字符串值。而屬性 ``__bases__`` 則是一個tuple,里面是各個給定的基類對象。舉個例子,使用剛才創(chuàng)建的 ``C1`` :
::
print C1.__name__, C1.__base__
#prints: C1, (<type 'object'>,)
一個類還有個屬性 ``__dict__`` ,它是一個字典對象,類用它來存放所有其他的屬性。對于任意類對象 ``C`` ,任意對象 ``x`` ,和任意標識符 ``s`` (除了 ``__name__`` , ``__bases__`` 和 ``__dict__`` ), ``c.s`` 是和 ``c.__dict__['s']=x`` 等價的。舉個例子,再次引用剛才創(chuàng)建的 ``C1`` :
::
C1.y=45
C1.__dict__['z'] = 67
print C1.x, C1.y, C1.z
#prints: 23, 45, 67
以下的設(shè)定是沒有區(qū)別的:像在類體內(nèi)定義屬性,或是在類體外通過指定一個屬性,再或者,在類體外,顯式地綁定到 ``c.__dict__`` 。
直接在類體中的語句,如果引用屬性時,要使用簡單的名字,而不是完全的名字。舉個例子:
::
class C3(object):
x = 23
y = x + 22
#must use just x,not C3.x
但是呢,如果是在類體中所定義的方法內(nèi),引用時就必須使用完整的名字,而不是簡單的名字。舉個例子:
::
class C4(object):
x = 23
def amethod(self):
print C4.x
# must use C4.x, not just x
注意,使用屬性引用時(也就是像 ``c.s`` )比函數(shù)綁定有更多的語義。具體參考“Attribute Reference Basics”。
5.1.3.2 Function definitions in a class body
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
大多數(shù)類體內(nèi)包含 ``def`` 語句,因為函數(shù)(在此處被稱為方法)對于很多類對象來說都是很重要的屬性。一個 ``def`` 語句盡管在類體中,但是仍然是和 “Function”的約定是一樣的。另外,在類體中的方法一直要有個強制的第一個參數(shù),約定稱為 ``self`` ,而他表示你調(diào)用方法所對應(yīng)的實例。在方法調(diào)用中, ``self`` 扮演一個很重要的角色。
這里是一個例子,一個類包含一個方法:
::
class C5(object):
def hello(self):
print "hello"
一個類可以定義多個特殊方法(就是方法的名字前后都有兩個下劃線),而它們又與實例的特殊操作有關(guān)。我在“Special Methods”中有詳細討論。
5.1.3.3 Class-private variables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
當一個類體中的語句(或者是一個方法)使用一個標識符以兩個下劃線開頭(但不是結(jié)尾),比如就像 ``__ident`` ,python就會隱式地將標識符轉(zhuǎn)成了 ``_classname__ident`` ,此次 ``classname`` 就是類的名字。這就可以使一個類使用私有的命名,以此減少無意中命名沖突的風險。
根據(jù)約定,所有以單下劃線起始的標識符,意味著對于綁定它們的作用域是私有的,不管這個作用域是不是一個類。python編譯器并不會強制進行轉(zhuǎn)換;這完全取決于編程人員來遵守它。
5.1.3.4 Class documentation strings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如果在類體中的第一條語句是一個字符串常量,那么編譯器會將其認為是這個類的文檔。這個屬性叫做 ``__doc__`` 而且也被叫做一個類的 *docstring* 。更多地參考 “Docstring”。
5.1.4 Descriptors
-----------------
一個描述符,就是一個新式的對象,而這個類提供了特殊的方法叫做 ``__get__`` 。描述符就是在語義上,控制一個實例屬性的訪問及設(shè)定。通俗的來講,就是當你訪問一個實例的屬性時,python會通過調(diào)用描述符的 ``__get__`` 來獲取屬性值。具體參考“Attribute Reference Basics”。
5.1.4.1 Overriding and nonoverriding descriptors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如果一個描述符類還提供了特殊方法 ``__set__`` ,那么這個描述符就是所謂的 *overriding descripter* (或者,一個舊式的,比較混亂的術(shù)語, *data descriptor* );如果這個描述符類僅僅提供了 ``__get__`` ,而沒有 ``__set__`` ,那么這個描述符就是所謂的 *nonoverriding descriptor* (或者是 *nondata* )。舉個例子,一個函數(shù)對象的類如果提供了 ``__get__`` ,但沒有 ``__set__`` ;那么,這個函數(shù)對象就是個 *nonoverriding descriptor* 。通俗地講,當你指派一個值給一個實例的屬性,而它又是一個 *overriding* 的描述符,那么python會調(diào)用 ``__set__`` 來賦值。
具體的參考“Attributes of instance object”。
舊式的類也可以有描述符,但是在舊式類中的描述符運行起來就好像是 *nonoverriding* 一樣(它們的 ``__set__`` 方法,就會被忽略)。
5.1.5 Instance
--------------
為創(chuàng)建一個類的實例,可以像調(diào)用函數(shù)一樣,調(diào)用那個類對象。每次調(diào)用都會返回一個新的實例,而它的類型就是那個類:
::
anInstance=C5()
你可以調(diào)用內(nèi)置函數(shù) ``isinstance(I,C)`` ,用一個類對象 ``C`` 作為參數(shù)。如果 ``isinstance`` 返回 ``True`` ,那么標識實例 ``I`` 是類 ``C`` 的實例或是其子類。不然, ``isinstance`` 返回 ``False`` 。
5.1.5.1 __init__
~~~~~~~~~~~~~~~~~~
當一個類定義了或繼承了一個名為 ``__init__`` 的方法,那么調(diào)用這個類對象時,就會隱式地執(zhí)行 ``__init__`` 來初始化一個新的實例。而調(diào)用時傳入的參數(shù)必須要和 ``__init__`` 的參數(shù)一致,除了第一個參數(shù) ``self`` 。舉個例子,考慮下面的類:
::
class C6(object):
def __init__(self,n):
self.x=n
這里是如何創(chuàng)建 ``C6`` 的實例:
::
anotherInstance = C6(42)
就像在 ``C6`` 中展示的, ``__init__`` 方法一般含有綁定實例屬性的語句。而且,一個 ``__init__`` 方法不能夠返回一個值;否則,python會拋出一個 ``TypeError`` 異常。
方法 ``__init__`` 的目的,是為了綁定,從而創(chuàng)建新建實例的屬性。你也可以綁定或解除實例的屬性在 ``__init__`` 的外面,就像你等等會看到的。但是,你的代碼會有更好的可讀性,如果你在 ``__init__`` 方法中初始化所有屬性的值。
當 ``__init__`` 是缺省的,那么你調(diào)用時不能給定參數(shù)。而且,新生成的實例沒有因?qū)嵗厥獾膶傩浴?/p>
5.1.5.2 Attributes of instance objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
一旦你創(chuàng)建了一個實例,那么你就可以訪問這個屬性(數(shù)據(jù)和方法)通過使用點( ``.`` )操作符。舉個例子:
::
anInstance.hello()
#prints: Hello
anotherInstance.x
#prints: 42
屬性引用有很豐富的語義,具體參考“Attribute Reference Basics”。
你可以給一個實例對象任意的屬性,通過綁定一個值給一個屬性引用。舉個例子:
::
class C7:
pass
z = C7()
z.x = 23
print z.x
#prints: 23
實例對象 ``z`` 現(xiàn)在就有一個名為 ``x`` 的屬性,綁定了值 ``23`` ,而 ``z.x`` 則指向那個屬性。注意特殊方法 ``__setattr__`` ,如果存在時,將會偵聽每個綁定屬性的行為。此外,對于一個新式的實例,如果你嘗試綁定屬性,而這個屬性又是一個 *overriding* 描述符,那么這個描述符的 ``__set__`` 將會監(jiān)聽。在這種情況下,語句 ``z.x=23`` 其實是執(zhí)行了 ``type(z).x.__set__(z,23)`` (舊式的實例將會忽略描述符的這個特性,也就是說,它們從未調(diào)用它們的 ``__set__`` 方法)。
創(chuàng)建一個實例時將隱式地設(shè)置兩個實例屬性。對于任何實例 ``z`` , ``z.__class__`` 是 ``z`` 所屬的類對象,而 ``z.__dict__`` 則是一個字典用來存放它的其它屬性。例如,對于實例 ``z`` 我們這樣創(chuàng)建:
::
print z.__class__.__name__, z.__dict__
#prints: C7, {'x':23}
你可能重新綁定(但不是解除)任意或全部的屬性,但是這很少是必需的。一個新式實例的 ``__class__`` 僅僅可能被重綁定到新式類上,而且一個 *legacy* 實例的 ``__class__`` 僅能綁定到 *legacy* 的類上。
對于任何實例 ``z`` ,對象 ``x`` ,和標識符 ``s`` (除了 ``__class__`` 和 ``__dict__`` ), ``z.s=x`` 等價于 ``z.__dict__['s']=x`` (除非有個特殊方法 ``__setattr__`` ,或者是一個 *overriding* 描述符的特殊方法 ``__set__`` ,將會監(jiān)聽嘗試綁定的行為)。舉個例子,重新使用剛才創(chuàng)建的 ``z`` :
::
z.y = 45
z.__dict__['z'] = 67
print z.x, z.y, z.z
#prints: 23, 45, 67
在實例創(chuàng)建屬性時使用 ``__init__`` 和顯示使用 ``z.__dict__`` 是一樣的。
5.1.5.3 The factory-function idiom
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
一個常見的任務(wù)是根據(jù)一些條件來創(chuàng)建不同類的實例,或者是防止創(chuàng)建一個新實例,假如已經(jīng)存在一個可復(fù)用的。一個常見的誤解就是,在 ``__init__`` 應(yīng)該返回一個對象,但是這是很難實現(xiàn)的:python會產(chǎn)生異常,當返回了任何值而不是 ``None`` 。最好的方式來執(zhí)行一個靈活對象的創(chuàng)建,是使用普通的函數(shù),而不是直接調(diào)用這個類對象。一個扮演這個角色的函數(shù)稱為一個 *factory function* 。
調(diào)用一個 *factory function* 是一個很靈活的方法:一個函數(shù)可能會返回一個已存在可復(fù)用的實例,或者是創(chuàng)建一個新的實例通過調(diào)用任何適合的類。加入你有兩個差不多可交換的類( ``SpecialCase`` 和 ``NormalCase`` ),而你希望依據(jù)參數(shù),靈活地創(chuàng)建合適的實例。下面的 *factory function* ``appropriateCase`` 就允許你做這樣的事情:
::
class SpecialCase(object):
def amethod(self):
print "special"
class NormalCase(object):
def amethod(self):
print "normal"
def appropriateCase(isnormal=True):
if isnormal:
return NormalCase()
else:
return SpecialCase()
aninstance = appropriateCase(isnormal=False)
aninstance.amethod()
#prints "special", as desired
5.1.5.4 __new__
~~~~~~~~~~~~~~~
每一個新式類都有(或繼承了)一個靜態(tài)方法( **static method** ) ``__new__`` 。當你調(diào)用 ``C(*args,**kwds)`` 來創(chuàng)建類 ``C`` 的新實例時,Python會首先調(diào)用 ``C.__new__(C,*args,**kwds)`` 。Python使用 ``__new__`` 的返回值 ``x`` 作為新創(chuàng)建的實例。然后,python會再調(diào)用 ``C.__init__(x,*args,**kwds)`` ,但是僅僅當 ``x`` 確實是 ``C`` 或它子類的實例時(不然,``x`` 的狀態(tài)仍處于 ``__new__`` 留給它的 )。因此,舉個例子,語句 ``x=C(23)`` 等同于:
::
x = C.__new__(C,23)
if isinstance(x,C):
type(x).__init__(x,23)
這里, ``object.__new__`` 將創(chuàng)建一個新的,為初始化的實例,并且接受這個類作為第一個參數(shù)。它將會忽略其他的參數(shù),如果它有一個 ``__init__`` 方法。但是如果它接受其他參數(shù)在這個參數(shù)之前,那么將會產(chǎn)生一個異常。當然如果沒有 ``__init__`` 方法也是會拋出異常的。當你在類體中重載了 ``__new__`` ,你不需要增加 ``__new__=staticmethod(__new__)`` ,就如你一般會喜歡的:python會識別這個名字 ``__name__`` 并且在它的上下文特殊地處理它。在那些不常有的情況下,如果你在類體外面重新綁定了 ``C.__new__`` 那么你就確實需要使用 ``C.__new__=staticmethod(whatever)`` 。
對于 ``__new__`` 有著大部分 **factory function** 的靈活性。 ``__new__`` 將會適時地選擇返回一個已存在的實例或創(chuàng)建一個新的實例。當 ``__new__`` 確實需要創(chuàng)建一個新的實例,它經(jīng)常會把創(chuàng)建的任務(wù)委托給 ``object.__new__`` 的調(diào)用或者是它其他父類的 ``__new__`` 的調(diào)用。下面的例子演示了如何重載靜態(tài)方法 ``__new__`` 為了實現(xiàn)一個 **Singleton** 設(shè)計模式:
::
class Singleton(object):
_singletons={}
def __new__(cls,*args,**kwds):
if cls not in cls._singletons:
cls._singletons[cls] = super(Singleton,cls).__new__(cls)
return cls._singletons[cls]
任何 ``Singleton`` 的子類(當然沒有重載 ``__new__`` ),就將只有一個實例。如果一個子類定義了一個 ``__init__`` 方法,這個子類必須確保它的 ``__init__`` 是安全的,當被重復(fù)調(diào)用時(在每次的創(chuàng)建請求下)。
舊式的類是沒有 ``__new__`` 方法的。
5.1.6 Attribute Reference Basics
--------------------------------
一個屬性引用就是形如 ``x.name`` 的表達式,這里 ``x`` 可以是任何表達式,而 ``name`` 是一個被稱為 *attribute name* 的標識符。很多種python對象都具有屬性,但是一個熟悉引用在 ``x`` 指向一個類或?qū)嵗龝r具有特殊的豐富的語義。記住,方法也是屬性,所以任何我們所謂的屬性同樣也適用于可調(diào)用的屬性(比如說方法)。
假如 ``x`` 是一個類 ``C`` 的實例,而它又是繼承自基類 ``B`` 。兩個類和實例有一些屬性(數(shù)據(jù)和方法),就像下面的:
::
class B(object):
a = 23
b = 45
def f(self):
print "method f in class B"
def g(self):
print "method g in class B"
class C(B):
b = 67
c = 89
d = 123
def g(self):
print "method g in class C"
def h(self):
print "method h in class C"
x = C()
x.d = 77
x.e = 88
一些屬性名字是特殊的。舉個例子, ``C.__name__`` 是字符串 ``'C'`` 而且是這個類名稱。 ``C.__bases__`` 則是一個元組 ``(B,)`` ,這是類 ``C`` 的基類。 ``x.__class__`` 是類 ``C`` ,它是 ``x`` 所屬的類。當你指向這些特殊的屬性,這個屬性引用將會訪問給定的位置,然后把它找到的值拿出來。你不能解除這些屬性。重新綁定是允許的,所以你可以改變名字或者是這個類的基類,或者是實例的類對象,匆忙地,但是這個高級的技術(shù)非同尋常的重要。
類 ``C`` 和實例 ``x`` 都各自有一個其他的特殊屬性:一個叫做 ``__dict__`` 的字典。全部其他的屬性,除了僅有的幾個特殊屬性外,全部都被放在類或?qū)嵗?``__dict__`` 中。
5.1.6.1 Getting an attribute from a class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
當你使用語法 ``C.name`` 來指向一個類 ``C`` 上的屬性,搜索會分成兩步進行:
1. 如果 ``name`` 就在 ``C.__dict__`` 中,那么 ``C.name`` 將會從 ``C.__dict__['name']`` 中取得值 ``v`` 。然后,如果 ``v`` 是一個描述符(也就是說, ``type(v)`` 提供了名為 ``__get__`` 的方法), ``C.name`` 的值將是調(diào)用 ``type(v).__get__(v,None,C)`` 的結(jié)果。否則, ``C.name`` 的值就是 ``v`` 。
2. 否則, ``C.name`` 就委托給 ``C`` 的基類來查找,意味著它會沿著 ``C`` 的祖先一個個查找 ``name`` (關(guān)于其查找順序,參考1.8.1)。
5.1.6.2 Getting an attribute from an instance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
但你使用 ``x.name`` 來指向類 ``C`` 的實例 ``x`` ,搜索會分成三步進行:
1. 當在 ``C`` (或者是在 ``C`` 的祖先類中)找到了 ``name`` ,作為一個 *overriding* 描述符 ``v`` 的名字(也就是說, ``type(v)`` 提供了 ``__get__`` 和 ``__set__`` 方法), ``C.name`` 的值將是調(diào)用 ``type(v).__get__(v,x,C)`` 的結(jié)果。(此步不適用于舊式實例)。
2. 否則,當 ``name`` 是在 ``x.__dict__`` 中的鍵值,那么 ``x.name`` 將會獲取并返回 ``x.__dict__['name']`` 的值。
3. 否則, ``x.name`` 將搜索委托給 ``x`` 的類(和搜索 ``C.name`` 的兩步是一樣的,就像剛才所說的 )。如果一個描述符 ``v`` 被找到了,那么屬性搜尋的結(jié)果,又將是 ``type(v).__get__(v,x,C)`` ;如果是一個非描述符值 ``v`` 被找到,那么結(jié)果就是 ``v`` 。
當這些搜索步驟結(jié)束后沒有找到屬性,python就會產(chǎn)生一個 ``AttributeError`` 的異常。但是,對于 ``x.name`` 的搜索,如果 ``C`` 定義或者繼承了特殊方法 ``__getattr__`` ,python將會調(diào)用 ``C.__getattr__(x,'name')`` 而不是產(chǎn)生一個異常(這完全取決于 ``__getattr__`` 是返回一個合適的值還是產(chǎn)生一個合適的異常,經(jīng)常是 ``AttributeError`` )。
考慮下面的屬性引用:
::
print x.e, x.d, x.c, x.b, x.a
#prints: 88, 77, 89, 67, 23
這里的 ``x.e`` 和 ``x.d`` 在實例搜尋的第二步就成功了,因為沒有涉及描述符,而且 ``e`` 和 ``d`` 都是在 ``x.__dict__`` 中的鍵值。所以,搜索不會繼續(xù),而是返回 ``88`` 和 ``77`` 。而其他三個則是到第三步,并且搜索了 ``x.__class__`` (也就是 ``C`` )。 ``x.c`` 和 ``x.b`` 是在類搜尋的第一步就成功了,因為 ``c`` 和 ``b`` 是在 ``C.__dict__`` 中。所以它們就返回了 ``89`` 和 ``67`` 。而 ``x.a`` 則是直到類搜索的第二步,搜索 ``C.__bases__[0]`` (也就是 ``B`` )。 ``a`` 是 ``B.__dict__`` 的鍵值,所以 ``x.a`` 終于成功并且返回 ``23`` 。
5.1.6.3 Setting an attribute
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
注意屬性搜尋的步驟,只有在你引用它時才會發(fā)生,而不是在你綁定一個屬性的時候。當你綁定(不管是類還是實例)一個屬性時,而它的名字不是特殊的(除非有個 ``__setattr__`` 方法,或者是一個 *overriding* 描述符的 ``__set__`` 方法,截取一個實例屬性的綁定),你僅僅影響了在 ``__dict__`` 中的屬性(在類或?qū)嵗校Q句話說,在屬性綁定的情況下,是沒有搜索被執(zhí)行的,除了檢查是否是個 *overriding* 描述符。
5.1.7 Bound and Unbound Methods
-------------------------------
一個函數(shù)對象的 ``__get__`` 方法返回一個包裝了函數(shù)的 *unbound method object* 或 *bound method object* 。在兩者間的不同點在于,一個 **unbound method** 沒有和一個特別的實例綁定,而 **bound method** 則有。
在前面段落的代碼中,屬性 ``f`` , ``g`` 和 ``h`` 都是函數(shù);所以,對于任一的屬性引用將會返回一個包裝了相應(yīng)函數(shù)的方法對象。考慮下面的:
::
print x.h, x.g, x.f, C.h, C.g, C.f
這個語句將輸出三個綁定方法,像這樣:
::
<bound method C.h of <__main__.C object at 0x8156d5c>>
然后是像這樣的三個未綁定方法:
::
<unbound method C.h>
當我們引用實例 ``x`` 的屬性時將返回綁定方法,而引用類 ``C`` 的屬性時則返回未綁定的方法。
因為一個綁定的方法已經(jīng)和一個特殊的實例綁定了,你可以這樣調(diào)用方法:
::
x.h()
#prints: method in class C
這里要注意的就是,你不需要傳給方法第一個參數(shù), ``self`` 。對于實例 ``x`` 的綁定方法會隱式地把對象 ``x`` 綁定給參數(shù) ``self`` 。因此,方法的主體能夠訪問實例的屬性,作為 ``self`` 的屬性,盡管我們沒有顯示的傳遞給方法這個參數(shù)。
一個未綁定的方法,是沒有和一個特殊實例關(guān)聯(lián)的,所以你必須指定一個合適的對象給第一個參數(shù),當你調(diào)用一個未綁定方法,舉個例子:
::
C.h(x)
#prints: method h in class C
你調(diào)用一個未綁定的方法比綁定方法次數(shù)要少的多。未綁定方法的主要用途是來訪問 *overridden* 方法;更好的是使用 ``super`` 。
5.1.7.1 Unbound method details
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
就像剛才我們討論的,當一個屬性引用是從類指向函數(shù)時,返回一個引用指向那個包裹了函數(shù)的未綁定方法。一個未綁定方法除了包裝的函數(shù)外還有三個額外的屬性: ``im_class`` 是提供方法的類對象, ``im_func`` 是被包裝的函數(shù),而 ``im_self`` 通常返回 ``None`` 。這些屬性是只讀的,這意味著嘗試重綁定或解除綁定都會拋出異常。
你可以調(diào)用一個未綁定的方法就如同你調(diào)用了它的 ``im_func`` 函數(shù),但是第一個參數(shù)必須是是 ``im_class`` 的實例或是后裔。換句話說,對于未綁定方法的調(diào)用,必須至少有一個參數(shù),這個參數(shù)應(yīng)該和被包裝的函數(shù)的第一個參數(shù)一致(一般稱為 ``self`` )。
5.1.7.2 Bound method details
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
當一個屬性引用自一個實例,在搜索中,找到一個函數(shù)對象,而那是一個實例的類中的屬性,查找會調(diào)用函數(shù)的 ``__get__`` 方法來獲取屬性值。這個調(diào)用,在這種情況下,創(chuàng)建并返回了一個綁定的方法,并包裹著那個函數(shù)。
注意當一個屬性引用的查找在 ``x.__dict__`` 中找到了函數(shù)對象,那么屬性引用操作不會創(chuàng)建一個綁定方法,因為在這種情況下函數(shù)并不是當作一個描述符,而且,函數(shù)的 ``__get__`` 是不可調(diào)用的;更準確地說,函數(shù)對象本身就是屬性值。類似的,沒有一個綁定的方法是為不普通的函數(shù)調(diào)用而創(chuàng)建的,就如內(nèi)置的(與python代碼對照)函數(shù),因為它們不是描述符。
一個綁定方法,和一個未綁定方法是類似的,它們都有三個只讀的屬性,那是包裝函數(shù)對象多出來的。像在未綁定方法中, ``im_class`` 是一個提供方法的類對象,而 ``im_func`` 是被包裝的函數(shù)。但是,在一個綁定方法的對象中,屬性 ``im_self`` 指向獲得方法的實例 ``x`` 。
一個綁定方法使用起來像它的 ``im_func`` 函數(shù),但是調(diào)用綁定方法不需要顯式提供第一個參數(shù)(一般約定為 ``self`` )。當你調(diào)用一個綁定方法,在傳遞其他參數(shù)時,綁定方法把 ``im_self`` 傳遞給 ``im_func`` 的第一個參數(shù)。
讓我們看看下面這個底層的概念上的是如何調(diào)用 ``x.name(arg)`` 。在下面的例子中:
::
def f(a,b):
...
# a function f with two arguments
class C(object):
name = f
x = C()
這里 ``x`` 是一個類 ``C`` 的實例對象, ``name`` 是一個標識符,指明了 ``x`` 的方法( ``C`` 的屬性,它是一個函數(shù),在這里是函數(shù) ``f`` ),而 ``arg`` 是任何的表達式。python先檢查 ``name`` 是否是 ``C`` 中的屬性,并且它還是一個描述符。但是事實上它并不是,因為它們的類雖然定義了 ``__get__`` 方法,但是并不是 *overriding* 的描述符,因為它們沒有 ``__set__`` 方法。python接下來檢查 ``name`` 是否在 ``x.__dict__`` 中。但它們也不是。所以python在類 ``C`` 中查找 ``name`` (任何東西都會起作用以同樣的方式如果 ``name`` 被找到了,通過繼承,在 ``C`` 的 ``__bases__`` 中)。python注意到這個屬性值,也就是函數(shù)對象 ``f`` ,是一個描述符。所以,python調(diào)用 ``f.__get__(x,C)`` ,這就創(chuàng)建了一個綁定方法, ``im_func`` 設(shè)定為 ``f`` , ``im_class`` 被設(shè)定為 ``C`` ,而 ``im_self`` 被設(shè)為 ``x`` 。然后python調(diào)用這個綁定方法對象,以 ``arg`` 作為唯一的參數(shù)。這個綁定方法插入了 ``im_self`` (也就是 ``x`` )作為第一個參數(shù),而 ``arg`` 成為第二個,然后調(diào)用 ``im_func`` (也就是 ``f`` )。總的效果就像是這樣調(diào)用:
::
x.__class__.__dict__['name'](x,arg)
當一個綁定方法的函數(shù)體執(zhí)行了,它沒有特別的命名空間,不管是 ``self`` 或其他類。變量引用的是局部或全局的,就像其他的函數(shù)一樣。變量并不會隱式的指明 ``self`` 的屬性,或是任何其他類中的屬性。當一個方法需要指向,綁定,或解除綁定一個 ``self`` 對象的屬性,它也需要標準的屬性引用的語法(比如, ``self.name`` )。隱式的作用域可能會導(dǎo)致使用其他用過的,而python則不是這樣(因為python和其他面向?qū)ο蟛煌沁@使得python簡潔明了,而避免了混淆。
綁定方法對象是一級對象,你可以在任何你可以使用可調(diào)用對象的地方使用它們。因為綁定方法持有一個包裝函數(shù)的引用,并且指向它所執(zhí)行的 ``self`` 對象,它是更有用更靈活的選擇對應(yīng)嵌套的來說。一個實例對象的類提供了特殊的方法 ``__call__`` 提供了另一個可行的選擇。這些概念中的每一個使你綁定一些行為(代碼)和狀態(tài)(數(shù)據(jù))到一個單獨的可調(diào)用對象中。嵌套可能是最簡單的,但有很多的限制。這里是嵌套:
::
def make_adder_as_closure(augend):
def add(addend, _augend=augend):
return addend+_augend
return add
綁定方法和可調(diào)用的實例更加的豐富和靈活。這里是用綁定方法實現(xiàn)同樣的功能:
::
def make_adder_as_bound_method(augend):
class Adder(object):
def __init__(self,augend):
self.augend = augend
def add(self, addend):
return addend+self.augend
return Adder(augend).add
這里是以一個可調(diào)用實例來執(zhí)行(這個實例的類提供了特殊方法 ``__call__`` ):
::
def make_adder_as_callable_instance(augend):
class Adder(object):
def __init__(self,augend):
self.augend=augend
def __call__(self,addend):
return addend+self.augend
return Adder(augend)
從這些代碼調(diào)用函數(shù)來看,它們是可交換的,因為它們都返回多態(tài)的可調(diào)用對象(也就是都是可用的)。在這里,嵌套是最簡單的;而其他兩種則是更靈活,普遍而且更強大的機制,但是在這個簡單的例子中沒必要使用這么強大的功能。
5.1.8 Inheritance
-----------------
當你使用一個屬性引用 ``C.name`` ,但是 ``name`` 不在 ``C.__dict__`` 中,搜尋會隱式的在 ``C.__bases__`` 以特殊的順序執(zhí)行(歷史原因,稱為 *method resolution order* ,或者是 **MRO** ,甚至是對于任何屬性,而不僅僅是對方法)。 ``C`` 的基類會又會依次的搜尋它們的基類。搜尋機制檢查直接或間接的祖先,一個又一個,以 **MRO** 進行,當 ``name`` 被找到時將會停止。
5.1.8.1 Method resolution order
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在一個類中,屬性名字的搜尋本質(zhì)上通過以 **left-to-right** , **depth-first** 的順序訪問祖先發(fā)生。但是,在多繼承的存在下(這樣就使繼承的圖表是一個非循環(huán)的圖表,而不是一個樹形的),這個簡單的方法可能導(dǎo)致一些祖先類被訪問了兩次。在這樣的情況下,這種分解的順序被闡明,通過在搜尋的列表留下右邊的那個類,這將會持續(xù)到搜索完,然后再去剛才剩下的那些類中重新開始搜索。這個就使得多類繼承很難正確有效的使用。而新式對象則在此方面更加高級。
這在 **left-right** , **depth-first** 搜索中個問題,可以很簡單的在舊式類中被論證:
::
class Base1:
def amethod(self):
print "Base1"
class Base2(Base1):
pass
class Base3:
def amethod(self):
print "Bases3"
class Derived(Base2,Base3):
pass
aninstance=Derived()
aninstance.amethod()
#prints: "Base1"
在這個情況下, ``amethod`` 的搜尋將從 ``Derived`` 開始。當沒有在此處找到,將會去搜索 ``Base2`` 。因為在此處也沒有找到,舊式的搜尋將會繼續(xù)搜索它的父類, ``Base1`` ,而在這里找到了那個屬性。所以,就是類將會停止,而不在考慮 ``Base3`` ,而在這里也可以找到那個屬性。而新式的 **MRO** 則解決了那個問題,通過移除最左邊的 ``Base1`` ,所以將會搜索 ``Base3`` 中的 ``amethod`` 。
Figure 5-1 展示了舊式和新式的 **MRO** ,在這個菱形的繼承圖中。
.. figure:: pythonian_0501.jpg
:align: center
**Figure 5-1. Legacy and new-style MRO**

每一個新式的類和內(nèi)置類型都有一個特殊制度的類屬性,叫做 ``__mro__`` ,這是一個用于方法分解的元組,以一定順序存放類型。你只可以在類中引用 ``__mro__`` ,而不以在實例上,而且因為 ``__mro__`` 是只讀的屬性,你不可以重新綁定或解除綁定。具體的,你可以參考 Michele Simionato 寫的 “The Python 2.3 Method Resolution Order” ,在 http://www.python.org/2.3/mro.html 。
5.1.8.2 Overriding attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
就像我們剛看到的,一個屬性的搜尋會依照 **MRO** (典型的是根據(jù)繼承樹)然后在找到時就會停止。而派生類會比他們的父類先被搜尋,這樣子,當子類和父類都定義了相同名字的屬性時,搜索就會在子類中尋到定義并在那里停止。這就是所謂的 **覆寫** 了父類中的定義。考慮下面的例子:
::
class B(object):
a=23
b=45
def f(self):
print "method f in class B"
def g(self):
print "method g in class B"
class C(B):
b=67
c=89
d=123
def g(self):
print "method g in class C"
def h(self):
print "method h in class C"
在這段代碼中,類 ``C`` 覆寫了超類 ``B`` 的屬性 ``b`` 和 ``g`` 。注意,不像其他的語言,python中你可以覆寫數(shù)據(jù)屬性,也可以是可調(diào)用的屬性(方法)。
5.1.8.3 Delegating to superclass methods
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
當一個子類 ``C`` 覆寫了它的超類 ``B`` 中的一個方法 ``f`` 時, ``C.f`` 的主體經(jīng)常需要把某些操作委托給超類中的方法來執(zhí)行。這個可以使用未綁定方法,像下面:
::
class Base(object):
def greet(self,name):
print "Welcome ",name
class Sub(Base):
def greet(self,name):
print "Well Met and",
Base.greet(self,name)
x=Sub()
x.greet('Alex')
在 ``Sub.greet`` 的主體中,超類的委托,通過屬性引用 ``Base.greet`` 使用了未綁定方法,所以正常的傳遞了所有的屬性,包括 ``self`` 。未綁定方法最常用的就是在委托超類執(zhí)行中。
一個很常見的委托就是在特殊方法 ``__init__`` 中。當python創(chuàng)建一個實例時,基類的 ``__init__`` 方法不會自動調(diào)用,就像在其他一些面向?qū)ο蟮恼Z言中一樣。因此,必要時,就依靠于使用委托來完成合適的初始化。舉個例子:
::
class Base(object):
def __init__(self):
self.anattribute = 23
class Derived(Base):
def __init__(self):
Base.__init__(self)
self.anotherattribute=45
如果在類 ``Derived`` 的 ``__init__`` 中未顯式調(diào)用類 ``Base`` , ``Derived`` 的實例將會丟失初始化的部分信息,所以他將缺少屬性 ``anotherattribute`` 。
5.1.8.4 Cooperative superclass method calling
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
使用未綁定方法調(diào)用超類的方法,可能在多重繼承上出問題,特別是菱形狀的。考慮下面的定義:
::
class A(object):
def met(self):
print 'A.met'
class B(A):
def met(self):
print 'B.met'
A.met(self)
class C(A):
def met(self):
print 'C.met'
A.met(self)
class D(B,C):
def met(self):
print 'D.met'
B.met(self)
C.met(self)
在這個代碼中,當我們調(diào)用 ``D().met()`` , ``A.met`` 將會出現(xiàn)兩次。那我們?nèi)绾未_保祖先只被調(diào)用一次,而且僅僅一次?解決的辦法就是,使用內(nèi)置的 ``super`` 。 ``super(aclass,obj)`` ,將會返回對象 ``obj`` 的特殊超級對象。當我們搜尋一個屬性(比如一個方法)在這個超級對象中,搜尋將會開始于在 ``obj`` 的 **MRO** 的類 ``aclass`` 中。所以我們可以這樣改寫先前的代碼:
::
class A(object):
def met(self):
print 'A.met'
class B(A):
def met(self):
print 'B.met'
super(B,self).met()
class C(A):
def met(self):
print 'C.met'
super(C,self).met()
class D(B,C):
def met(self):
print 'D.met'
super(D,self).met()
現(xiàn)在, ``D().met()`` 對每個類的 ``met`` 都只調(diào)用了一次。如果你形成了在使用超類時使用 ``super`` 的習慣,那么盡管可能有很復(fù)雜的結(jié)構(gòu),你的類也能很好的使用。無論這個繼承結(jié)果有多簡單,使用這個并沒有壞處,而且,我也推薦使用新式的對象模型。
你使用未綁定方法的技術(shù)的唯一的情況,可能就是,類之間的方法有不相容的簽名,但如果你真的要處理這種東西,那么使用未綁定方法的技術(shù)可能會至少有點討厭。多重繼承的合適使用可能會有限制。但是,就算OOP的大多數(shù)基本的特征,像在基類與子類間的多態(tài)將會因為簽名的不一致導(dǎo)致有所削減。
5.1.8.5 "Deleting" class attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
繼承和覆寫提供了簡單有效的方式來增加或修改類屬性(特別是方法)但不帶侵略性的(也就是說,不會修改那個定義了屬性的類)通過在子類中增加或覆寫屬性。但是,繼承并不提供來刪除或隱藏基類的屬性的方式但不帶侵略性的。如果子類定義或覆寫一個屬性失敗了,python就會尋找基類的定義。如果你需要執(zhí)行這樣的刪除,那么可能包含:
* 覆寫方法并在方法內(nèi)產(chǎn)生異常。
* 避開繼承,擁有這個屬性在其他地方而不是在子類的 ``__dict__`` ,而且定義 ``__getattr__`` 來有選擇的委托。
* 使用新式對象模型,而且覆寫 ``__getattribute__`` 實現(xiàn)相同的效果。
5.1.9 The Built-in object Type
------------------------------
內(nèi)置的 ``object`` 類型是全部內(nèi)置類型和新式類的祖先。這個 ``object`` 類型定義了一些特殊的方法,它們執(zhí)行對象的默認語義。
``__new__`` ``__init__``
你可以創(chuàng)建一個 ``object`` 的直接實例,通過不帶參數(shù)的調(diào)用 ``object()`` 。調(diào)用將會隱式地使用 ``object.__new__`` 和 ``object.__init__`` 來創(chuàng)建和返回一個沒有屬性的實例對象(甚至沒有存放屬性的 ``__dict__`` )。這個實例對象就像一個“哨兵”一樣有用,保證不和其他不同的對象不相同。
``__delattr__`` ``__getattribute__`` ``__setattr__``
默認的,一個對象操作屬性引用通過使用 ``object`` 的這些方法。
``__hash__`` ``__repr__`` ``__str__``
任意對象可以傳遞給函數(shù) ``hash`` 和 ``repr`` 和傳給類型 ``str`` 。
一個 ``object`` 的子類會覆寫這些方法或增加其他。
5.1.10 Class-Level Methods
--------------------------
Python提供了兩種內(nèi)置的 **nonoverriding** 描述符類型,這就給了一個類兩種不同的“類級方法(classlevel method)”。
5.1.10.1 Static methods
~~~~~~~~~~~~~~~~~~~~~~~
一個 **static method** 是一個方法,你可以在類上或者在類的任意實例調(diào)用,但沒有特殊的行為和普通方法的約束,綁定和未綁定,而且關(guān)注第一個參數(shù)。一個靜態(tài)方法可以有任意的簽名;它可以沒有參數(shù),而第一個參數(shù)沒有什么作用。你可以認為靜態(tài)方法就是一個普通的函數(shù),你可以正常的調(diào)用,盡管它事實上是一個綁定到類的方法。當它并不是必須要定義靜態(tài)方法時(你總可以定義一個正常的函數(shù)取代之),一些程序員認為它們是一個精致的選擇當一個函數(shù)的目的是更緊的綁定到一些特殊類上。
要創(chuàng)建一個靜態(tài)方法,調(diào)用內(nèi)置類型 ``staticmethod`` 然后把它的結(jié)果綁定到一個類屬性。像所有的屬性綁定,通常是在類體中被完成的,當然你也可以放在其他位置。唯一要給 ``staticmethod`` 的參數(shù)就是當python調(diào)用這個靜態(tài)方法時要調(diào)用的函數(shù)。下面的例子展示了如何定義和調(diào)用一個靜態(tài)方法:
::
class AClass(object):
def astatic():
print 'a static method'
astatic=staticmethod(astatic)
anInstance=AClass()
AClass.astatic()
#prints: a static method
anInstance.astatic()
#prints: a static method
這個例子對于要傳遞給靜態(tài)方法的函數(shù)和靜態(tài)方法返回的結(jié)果使用相同的名字。這個方式并不是強制的,但是個好的主意,而且我也推薦使用。在Python 2.4中提供了特殊,簡單的語法,可以參考 **裝飾符** 。
5.1.10.2 Class methods
~~~~~~~~~~~~~~~~~~~~~~
一個類方法是一個方法,你可以在類或其任意實例中調(diào)用。Python把你所調(diào)用方法的類綁定給了這個方法的第一個參數(shù),或者也可以所調(diào)用方法的實例的類;它并不會像綁定方法一樣,把實例綁定給第一個參數(shù)。對于類方法來說,是沒有等同的未綁定方法的。第一個參數(shù)一般約定為 ``cls`` 。雖然定義一個類方法不是必須的(你可以定義一個函數(shù),然后接受類對象作為第一個參數(shù)),一些程序員認為對于這些函數(shù)來說是不錯的選擇。
要創(chuàng)建一個類方法,調(diào)用內(nèi)置類型 ``classmethod`` 然后再把結(jié)果綁定到類屬性上。也許所有的屬性綁定,經(jīng)常將其寫在類體中,但你也可以在其他地方實現(xiàn)。而唯一的參數(shù)就是這個要作為類方法調(diào)用的函數(shù)。下面是定義和調(diào)用:
::
class ABase(object):
def aclassmet(cls):
print 'a class method for',cls.__name__
aclassmet=classmethod(aclassmet)
class ADeriv(ABase):
pass
bInstance=ABase()
dInstance=ADeriv()
ABase.aclassmet()
#prints: a class method for ABase
bInstance.aclassmet()
#prints: a class method for ABase
ADeriv.aclassmet()
#prints: a class method for ADeriv
dInstance.aclassmet()
#prints: a class method for ADeriv
同樣,和上一小節(jié)所說,可以使用 **裝飾符** 。
5.1.11 Properties
-----------------
python提供一個內(nèi)置的 **overriding** 描述符,你可以用來給出一個類的實例 **property** 。
一個 **property** 是一個實例的屬性,有著特殊的功能。你引用,綁定,解綁定一個屬性可以用一個普通的語法(也就是 ``print x.prop`` ,``x.prop=23`` , ``del x.prop`` )。但是,不僅僅是這些普通的語義,你還可以給其指定特殊的方法。下面是定義一個只讀的屬性:
::
class Rectangle(object):
def __init__(self,width,height):
self.width=width
self.height=height
def getArea(self):
return self.width*self.height
area=property(getArea,doc='area of the rectangle')
每一個類 ``Rectangle`` 的實例 ``r`` 都有個只讀的屬性 ``r.area`` ,通過調(diào)用方法 ``r.getArea()`` 把矩形的兩邊相乘。而docstring ``Rectangle.area.__doc__`` 則是 ```area of the rectangle``` 。屬性 ``r.area`` 是只讀的(嘗試重綁定或解綁定都會失敗)因為我們只給 ``property`` 一個 ``get`` 方法,而沒有 ``set`` 或 ``del`` 方法。
屬性和特殊方法 ``__getattr__`` , ``__setattr__`` , ``__delattr__`` 的工作差不多,但是方法更快更簡單。你可以創(chuàng)建一個屬性通過調(diào)用內(nèi)置的 ``property`` 并把結(jié)果綁定給類屬性。想其他的屬性,一般是放在類體中,當然你也可以放其他地方。在類 ``C`` 內(nèi),使用這樣的語法:
::
attrib=property(fget=None,
fset=None, fdel=None,
doc=None)
當 ``x`` 是 ``C`` 的實例,你又引用 ``x.attrib`` ,python會調(diào)用 ``fget`` 給屬性構(gòu)造器,但沒有參數(shù)。當以 ``x.attrib=value`` 賦值是,python會調(diào)用 ``fset`` ,并把 ``value`` 作為唯一傳人的參數(shù)。而當執(zhí)行 ``del x.attrib`` ,python又會執(zhí)行 ``fdel`` ,不傳人任何參數(shù)。python又會將 ``doc`` 的內(nèi)容作為 ``doctstring`` 。 ``property`` 的全部參數(shù)都是 *可選的* 。當一個參數(shù)沒有,那么這個操作就被認為是禁止的(此時,python會產(chǎn)生一個異常)。舉個例子,剛才我們使 ``Rectangle`` 的屬性 ``area`` 只讀,那是因為我們只提供了 ``fget`` ,而沒有 ``fset`` 或 ``fdel`` 。
5.1.11.1 Why properties are important
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
屬性的重要性在于,它們的存在使得非常安全,而且你可以使這個屬性作為公共接口。如果這變得必要的,在你以后的版本中可能需要這個屬性多態(tài)運行,或是在被引用、重綁定或解綁定時調(diào)用其他的一些代碼,你知道你將會將簡單的屬性變成 **property** ,而且得到想要的效果但不對其他代碼產(chǎn)生影響。這就使你避免笨的特色,像一個 *accessor* 或 *mutator* 方法,這是在缺少 **property** 或等同機制的OOp中經(jīng)常需要的。舉個例子,客戶代碼可以使用下面自然的語法:
::
someInstance.widgetCounter += 1
而不是像下面這種嵌套的形式:
::
someInstance.setWidgetCounter(someInstance.getWidgetCounter()+1)
如果編寫代碼時,你考慮使用 ``getThis`` 或 ``setThis`` 的名字,那么就考慮使用 **property** 來變得清楚。
5.1.11.2 Properties and inheritance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**property** 也可以像其他屬性那樣繼承。但是,這里可能有點小陷阱: **property** 所調(diào)用的是它所在類中的方法,而不是其子類的方法。舉個例子:
::
class B(object):
def f(self):
return 23
g=property(f)
class C(B):
def f(self):
return 42
c=C()
print c.g
#prints 23, not 42
屬性 ``c.g`` 調(diào)用了 ``B.f`` ,而不是你直覺上所想的 ``C.f`` 。這個原因很簡單:因為 **property** 是通過傳遞函數(shù)對象 ``f`` 創(chuàng)建的(而且是在 ``B`` 被執(zhí)行時所創(chuàng)建,所以這個時候,這個函數(shù)對象就被認為是 ``B.f`` )。事實上, ``f`` 在子類 ``C`` 中被重新定義了,但是因為 **property** 并沒有搜尋到它,而只是使用了先前的那個。如果你需要解決這個問題,那么就需要引入一個間接層:
::
class B(object):
def f(self):
return 23
def _f_getter(self):
return self.f()
g=property(_f_getter)
class C(B):
def f(self):
return 42
c=C()
print c.g
#prints 42, as expected
這里,被 **property** 所持有的函數(shù)對象是 ``B._f_getter`` ,它在執(zhí)行時會搜尋到 ``f`` (因為它調(diào)用了 ``self.f()`` );所以,覆寫的 ``f`` 會起效果。
5.1.12 __slots__
----------------
一般來說,任何類 ``C`` 的每個實例對象 ``x`` 都有一個字典 ``x.__dict__`` ,這是python用來讓你任意綁定屬性給 ``x`` 的。為了節(jié)省點內(nèi)存(讓 ``x`` 僅使用預(yù)定義的屬性),你可以定義一個新式類 ``C`` 的 ``__slots__`` 屬性,這是一個字符串(通常是表示符)序列(通常是元組)。當一個新式類 ``C`` 有個屬性 ``__slots__`` 時,一個類 ``C`` 的直接實例 ``x`` 是沒有 ``x.__dict__`` 的,而且嘗試綁定不在 ``C.__slots__`` 中的屬性將會產(chǎn)生異常。使用了 ``__slots__`` 就使你減少了內(nèi)存的消耗,對于小的實例對象,這是很值得的,當有很多這樣的對象時,就節(jié)省了大大的空間。但不像大多數(shù)的屬性, ``__slots__`` 只有當一些類體中的語句把它綁定為類屬性時才有效。后來修改 ``__slots__`` 是不起效果的,繼承也是。這里是把 ``__slots__`` 增加到 ``Rectangle`` 中:
::
class OptimizedRectangle(Rectangle):
__slots__='width','height'
我們不需要給 ``area`` 定義個 *slot* 。 ``__slots__`` 中并不放 **property** ,僅僅是普通的實例對象,這些屬性如果沒有在 ``__slots__`` 中定義,將會搜尋 ``__dict__`` 。
5.1.13 __getattribute__
-----------------------
對于新式的實例,實例屬性的引用都是通過特殊方法 ``__getattribute__`` 執(zhí)行的。這個方法是基類 ``object`` 所提供的,它執(zhí)行了所有具體的屬性引用。但是,你也可以覆寫這個 ``__getattribute__`` 方法,像隱藏被繼承類的屬性。下面的例子展示了在新式對象模型中一個沒有 ``append`` 的鏈表:
::
class listNoAppend(list):
def __getattribute__(self,name):
if name=='append':
raise AttributeError,name
return list.__getattribute__(self,name)
類 ``listNoAppend`` 的實例 ``x`` 幾乎和內(nèi)置的鏈表對象是一樣的,除了當調(diào)用 ``x.append`` 時將產(chǎn)生異常。
5.1.14 Per-Instance Methods
---------------------------
不管舊式或是新式的對象模型,都允許一個實例,擁有一個實例特化的屬性綁定,包括可調(diào)用的屬性。對于一個方法,就像其他的屬性(除了那些新式類中的 **overriding** 描述符),一個實例特化綁定隱藏了一個類級的綁定:屬性搜索如果在實例中找到了就不會考慮類了。在兩種對象模型中,對于可調(diào)用屬性的一個實例特化的綁定不會進行什么變換。換句話說,一個屬性引用返回相同的可調(diào)用對象,而且已經(jīng)在先前已經(jīng)綁定。
舊式和新式的對象模型的確有區(qū)別,在每個實例綁定的效果上,python會隱式地調(diào)用。在經(jīng)典的的對象模型中,一個實例一般會覆寫一個特殊方法,然后python在隱式調(diào)用方法時使用每個實例的綁定。在新式的對象模型上,特殊方法的隱式使用一般依賴于特殊方法的類級綁定,如果有的話。下面的代碼展示了兩者的區(qū)別:
::
def fakeGetItem(idx):
return idx
class Classic:
pass
c=Classic()
c.__getitem__=fakeGetItem
print c[23]
# prints: 23
class NewStyle(object):
pass
n=NewStyle()
n.__getitem__=fakeGetItem
print n[23]
#prints in:
# Traceback (most recent call last):
# File "<stdin>", line 1, in ?
# TypeError: unindexable object
經(jīng)典對象模型的語義在這個方面可能比較有用。但是,新式對象模型的方法更普遍,而且它調(diào)整和簡化了類和元類的關(guān)系。
5.1.15 Inheritance from Built-in Types
--------------------------------------
一個新式類可以從內(nèi)置類型派生。但是,一個類可能直接或間接從多個內(nèi)置的類型派生,如果這些類型是特殊設(shè)計的,來允許這個層次的兼容。python不支持不受拘束的繼承從多個任意的內(nèi)置對象中。一般的,一個新式類只能以其中一種派生,當然也包括 ``object`` ,這是所有的類型的超類。舉個例子:
::
class noway(dict,list):
pass
將會產(chǎn)生一個 ``TypeError`` 異常,具體會解釋為“當調(diào)用元類的基類有錯誤:多基類有實例的沖突”。如果你曾經(jīng)看過這樣的錯誤,這就意味著你嘗試繼承,直接或間接,從多個內(nèi)置類型派生,但他們并不是設(shè)定為可合作的。