類將數(shù)據(jù)和功能捆綁在一起澡匪。創(chuàng)建新類時創(chuàng)建新類型的對象努溃,允許創(chuàng)建該類型的新實例劲装。每個類實例都可以附加屬性以保持其狀態(tài)凌那。類實例也可以有方法(由類定義)來修改其狀態(tài)兼雄。
與其他編程語言相比,Python的類機制語法和語義最少帽蝶。它是C ++和Modula-3中的的混合體赦肋。 Python類提供了面向對象編程的所有標準功能:類繼承機制支持多個基類,派生類可以重寫其基類方法励稳,并且可以調用具有相同名稱的基類的方法佃乘。對象可以包含任意數(shù)量和種類的數(shù)據(jù)。與模塊一樣驹尼,類也具有Python的動態(tài)特性:它們是在運行時創(chuàng)建的趣避,并且可以在創(chuàng)建后修改。
在C ++術語中新翎,常規(guī)類成員(包括數(shù)據(jù)成員)是公共的(除了見下面的私有變量)程帕,并且所有的成員函數(shù)都是虛擬的。和Modula-3一樣地啰,從它的方法中引用對象的成員沒有簡短的方法:方法函數(shù)聲明了明確的第一參數(shù)愁拭,它代表對象,它由調用隱式提供亏吝。就像在Smalltalk中一樣岭埠,類本身就是對象。這為導入和重命名提供了語義蔚鸥。與C ++和Modula-3不同枫攀,內置類型可以用作基類,以便用戶進行擴展株茶。此外来涨,與C ++類似,可以為類實例重新定義大多數(shù)具有特殊語法(算術運算符启盛,下標等)的內置運算符蹦掐。
(由于缺乏普遍接受的術語來討論類,我偶爾會使用Smalltalk和C ++術語僵闯,因為它的面向對象語義與更接近卧抗,所以我會使用Modula-3術語,但很少有讀者聽說過鳖粟。)
名稱和對象
對象具有特性社裆,并且多個名稱(在多個作用域中)可以綁定在同一個對象上。在其它語言中被稱為別名向图。Python不可變基礎類型(數(shù)值泳秀,字符串标沪,元組)是傳值的,但是列表嗜傅、字典這類可變對象金句,或者大多數(shù)程序外部類型(文件,窗體等)描述實體時是傳地址的吕嘀,像是指針违寞。例如,你可以輕易的傳遞一個對象偶房,因為通過繼承只是傳遞一個指針趁曼。
作用域和命名空間
類的定義非常巧妙的運用了命名空間。
命名空間 是從命名到對象的映射棕洋。當前命名空間主要是通過 Python 字典實現(xiàn)的挡闰,不過通常不關心具體的實現(xiàn)方式(除非出于性能考慮),以后也有可能會改變其實現(xiàn)方式拍冠。以下有一些命名空間的例子:內置命名(像 abs() 這樣的函數(shù)尿这,以及內置異常名)集簇抵,模塊中的全局命名庆杜,函數(shù)調用中的局部命名。某種意義上講對象的屬性集也是一個命名空間碟摆。關于命名空間需要了解的一件很重要的事就是不同命名空間中的命名沒有任何聯(lián)系晃财,例如兩個不同的模塊可能都會定義一個名為 maximize
的函數(shù)而不會發(fā)生混淆-用戶必須以模塊名為前綴來引用它們。
順便提一句典蜕,我稱 Python 中任何一個“.”之后的命名為 屬性 --例如断盛,表達式 z.real
中的 real
是對象 z
的一個屬性。嚴格來講愉舔,從模塊中引用命名是引用屬性:表達式 modname.funcname
中钢猛,modname
是一個模塊對象,funcname
是它的一個屬性轩缤。因此命迈,模塊的屬性和模塊中的全局命名有直接的映射關系:它們共享同一命名空間![1]
屬性可以是只讀過或寫的火的。后一種情況下壶愤,可以對屬性賦值。你可以這樣作: modname.the_answer = 42
馏鹤≌鹘罚可寫的屬性也可以用 del 語句刪除。例如: del modname.the_answer
會從 modname
對象中刪除 the_answer
屬性湃累。
不同的命名空間在不同的時刻創(chuàng)建勃救,有不同的生存期碍讨。包含內置命名的命名空間在 Python 解釋器啟動時創(chuàng)建,會一直保留剪芥,不被刪除垄开。模塊的全局命名空間在模塊定義被讀入時創(chuàng)建,通常税肪,模塊命名空間也會一直保存到解釋器退出溉躲。由解釋器在最高層調用執(zhí)行的語句,不管它是從腳本文件中讀入還是來自交互式輸入益兄,都是 main 模塊的一部分锻梳,所以它們也擁有自己的命名空間(內置命名也同樣被包含在一個模塊中,它被稱作 builtins )净捅。
當調用函數(shù)時疑枯,就會為它創(chuàng)建一個局部命名空間,并且在函數(shù)返回或拋出一個并沒有在函數(shù)內部處理的異常時被刪除蛔六。(實際上荆永,用遺忘來形容到底發(fā)生了什么更為貼切。)當然国章,每個遞歸調用都有自己的局部命名空間具钥。
作用域 就是一個 Python 程序可以直接訪問命名空間的正文區(qū)域。這里的直接訪問意思是一個對名稱的錯誤引用會嘗試在命名空間內查找液兽。盡管作用域是靜態(tài)定義骂删,在使用時他們都是動態(tài)的。每次執(zhí)行時四啰,至少有三個命名空間可以直接訪問的作用域嵌套在一起:
-
包含局部命名的使用域在最里面宁玫,首先被搜索;其次搜索的是中層的作用域柑晒,這里包含了同級的函數(shù)欧瘪;
最后搜索最外面的作用域,它包含內置命名匙赞。
首先搜索最內層的作用域佛掖,它包含局部命名任意函數(shù)包含的作用域,是內層嵌套作用域搜索起點罚屋,包含非局部苦囱,但是也非全局的命名
接下來的作用域包含當前模塊的全局命名
最外層的作用域(最后搜索)是包含內置命名的命名空間
如果一個命名聲明為全局的,那么對它的所有引用和賦值會直接搜索包含這個模塊全局命名的作用域脾猛。如果要重新綁定最里層作用域之外的變量撕彤,可以使用 nonlocal 語句;如果不聲明為 nonlocal,這些變量將是只讀的(對這樣的變量賦值會在最里面的作用域創(chuàng)建一個新的局部變量羹铅,外部具有相同命名的那個變量不會改變)蚀狰。
通常,局部作用域引用當前函數(shù)的命名职员。在函數(shù)之外麻蹋,局部作用域與全局使用域引用同一命名空間:模塊命名空間。類定義也是局部作用域中的另一個命名空間焊切。
重要的是作用域決定于源程序的意義:一個定義于某模塊中的函數(shù)的全局作用域是該模塊的命名空間扮授,而不是該函數(shù)的別名被定義或調用的位置,了解這一點非常重要专肪。另一方面刹勃,命名的實際搜索過程是動態(tài)的,在運行時確定的——然而嚎尤,Python 語言也在不斷發(fā)展荔仁,以后有可能會成為靜態(tài)的“編譯”時確定,所以不要依賴動態(tài)解析Q克馈(事實上乏梁,局部變量已經(jīng)是靜態(tài)確定了。)
Python 的一個特別之處在于:如果沒有使用 global 語法关贵,其賦值操作總是在最里層的作用域遇骑。賦值不會復制數(shù)據(jù),只是將命名綁定到對象坪哄。刪除也是如此:del x
只是從局部作用域的命名空間中刪除命名 x
质蕉。事實上势篡,所有引入新命名的操作都作用于局部作用域翩肌。特別是 import 語句和函數(shù)定義將模塊名或函數(shù)綁定于局部作用域(可以使用 global 語句將變量引入到全局作用域)。
global 語句用以指明某個特定的變量為全局作用域禁悠,并重新綁定它念祭。nonlocal 語句用以指明某個特定的變量為封閉作用域,并重新綁定它碍侦。
9.2.1. 作用域和命名空間示例
以下是一個示例粱坤,演示了如何引用不同作用域和命名空間,以及 global 和 nonlocal 如何影響變量綁定:
<pre>def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
</pre>
以上示例代碼的輸出為:
<pre>After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
</pre>
注意:local 賦值語句是無法改變 scope_test 的 spam 綁定瓷产。nonlocal 賦值語句改變了 scope_test 的 spam 綁定站玄,并且 global 賦值語句從模塊級改變了 spam 綁定。
你也可以看到在 global 賦值語句之前對 spam 是沒有預先綁定的濒旦。
9.3. 初識類
類引入了一些新語法:三種新的對象類型和一些新的語義株旷。
9.3.1. 類定義語法
類定義最簡單的形式如下:
<pre>class ClassName:
<statement-1>
.
.
.
<statement-N>
</pre>
類的定義就像函數(shù)定義( def 語句),要先執(zhí)行才能生效。(你當然可以把它放進 if 語句的某一分支晾剖,或者一個函數(shù)的內部锉矢。)
習慣上,類定義語句的內容通常是函數(shù)定義齿尽,不過其它語句也可以沽损,有時會很有用,后面我們再回過頭來討論循头。類中的函數(shù)定義通常包括了一個特殊形式的參數(shù)列表绵估,用于方法調用約定——同樣我們在后面討論這些。
進入類定義部分后卡骂,會創(chuàng)建出一個新的命名空間壹士,作為局部作用域。因此偿警,所有的賦值成為這個新命名空間的局部變量躏救。特別是函數(shù)定義在此綁定了新的命名。
類定義完成時(正常退出)螟蒸,就創(chuàng)建了一個 類對象盒使。基本上它是對類定義創(chuàng)建的命名空間進行了一個包裝七嫌;我們在下一節(jié)進一步學習類對象的知識少办。原始的局部作用域(類定義引入之前生效的那個)得到恢復,類對象在這里綁定到類定義頭部的類名(例子中是 ClassName
)诵原。
9.3.2. 類對象
類對象支持兩種操作:屬性引用和實例化英妓。
屬性引用 使用和 Python 中所有的屬性引用一樣的標準語法:obj.name
。類對象創(chuàng)建后绍赛,類命名空間中所有的命名都是有效屬性名蔓纠。所以如果類定義是這樣:
<pre>class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
</pre>
那么 MyClass.i
和 MyClass.f
是有效的屬性引用,分別返回一個整數(shù)和一個方法對象吗蚌。也可以對類屬性賦值腿倚,你可以通過給 MyClass.i
賦值來修改它。 __doc__
也是一個有效的屬性蚯妇,返回類的文檔字符串:"A simple example class"
敷燎。
類的 實例化 使用函數(shù)符號。只要將類對象看作是一個返回新的類實例的無參數(shù)函數(shù)即可箩言。例如(假設沿用前面的類):
<pre>x = MyClass()
</pre>
以上創(chuàng)建了一個新的類 實例 并將該對象賦給局部變量 x
硬贯。
這個實例化操作(“調用”一個類對象)來創(chuàng)建一個空的對象。很多類都傾向于將對象創(chuàng)建為有初始狀態(tài)的陨收。因此類可能會定義一個名為 __init__()
的特殊方法饭豹,像下面這樣:
<pre>def init(self):
self.data = []
</pre>
類定義了 __init__()
方法的話,類的實例化操作會自動為新創(chuàng)建的類實例調用 __init__()
方法。所以在下例中墨状,可以這樣創(chuàng)建一個新的實例:
<pre>x = MyClass()
</pre>
當然卫漫,出于彈性的需要,__init__()
方法可以有參數(shù)肾砂。事實上列赎,參數(shù)通過 __init__()
傳遞到類的實例化操作上。例如镐确,
<pre>>>> class Complex:
... def init(self, realpart, imagpart):
... self.r = realpart
... self.i = imagpart
...
x = Complex(3.0, -4.5)
x.r, x.i
(3.0, -4.5)
</pre>
9.3.3. 實例對象
現(xiàn)在我們可以用實例對象作什么包吝?實例對象唯一可用的操作就是屬性引用。有兩種有效的屬性名源葫。
數(shù)據(jù)屬性 相當于 Smalltalk 中的“實例變量”或 C++ 中的“數(shù)據(jù)成員”诗越。和局部變量一樣,數(shù)據(jù)屬性不需要聲明息堂,第一次使用時它們就會生成嚷狞。例如,如果 x
是前面創(chuàng)建的 MyClass
實例荣堰,下面這段代碼會打印出 16 而在堆棧中留下多余的東西:
<pre>x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print(x.counter)
del x.counter
</pre>
另一種為實例對象所接受的引用屬性是 方法床未。方法是“屬于”一個對象的函數(shù)。(在 Python 中振坚,方法不止是類實例所獨有:其它類型的對象也可有方法薇搁。例如,鏈表對象有 append渡八,insert啃洋,remove,sort 等等方法屎鳍。然而宏娄,在后面的介紹中,除非特別說明哥艇,我們提到的方法特指類方法)
實例對象的有效名稱依賴于它的類绝编。按照定義僻澎,類中所有(用戶定義)的函數(shù)對象對應它的實例中的方法貌踏。所以在我們的例子中,x.f
是一個有效的方法引用窟勃,因為 MyClass.f
是一個函數(shù)祖乳。但 x.i
不是,因為 MyClass.i
不是函數(shù)秉氧。不過 x.f
和 MyClass.f
不同眷昆,它是一個 方法對象 ,不是一個函數(shù)對象。
9.3.4. 方法對象
通常亚斋,方法通過右綁定方式調用:
<pre>x.f()
</pre>
在 MyClass
示例中作媚,這會返回字符串 'hello world'
。然而帅刊,也不是一定要直接調用方法纸泡。 x.f
是一個方法對象,它可以存儲起來以后調用赖瞒。例如:
<pre>xf = x.f
while True:
print(xf())
</pre>
會不斷的打印 hello world
女揭。
調用方法時發(fā)生了什么?你可能注意到調用 x.f()
時沒有引用前面標出的變量栏饮,盡管在 f()
的函數(shù)定義中指明了一個參數(shù)吧兔。這個參數(shù)怎么了?事實上如果函數(shù)調用中缺少參數(shù)袍嬉,Python 會拋出異常--甚至這個參數(shù)實際上沒什么用……
實際上境蔼,你可能已經(jīng)猜到了答案:方法的特別之處在于實例對象作為函數(shù)的第一個參數(shù)傳給了函數(shù)。在我們的例子中伺通,調用 x.f()
相當于 MyClass.f(x)
欧穴。通常,以 n 個參數(shù)的列表去調用一個方法就相當于將方法的對象插入到參數(shù)列表的最前面后泵殴,以這個列表去調用相應的函數(shù)涮帘。
如果你還是不理解方法的工作原理,了解一下它的實現(xiàn)也許有幫助笑诅。引用非數(shù)據(jù)屬性的實例屬性時调缨,會搜索它的類。如果這個命名確認為一個有效的函數(shù)對象類屬性吆你,就會將實例對象和函數(shù)對象封裝進一個抽象對象:這就是方法對象弦叶。以一個參數(shù)列表調用方法對象時,它被重新拆封妇多,用實例對象和原始的參數(shù)列表構造一個新的參數(shù)列表伤哺,然后函數(shù)對象調用這個新的參數(shù)列表。
9.3.5. 類和實例變量
一般來說者祖,實例變量用于對每一個實例都是唯一的數(shù)據(jù)立莉,類變量用于類的所有實例共享的屬性和方法:
<pre>class Dog:
kind = 'canine' # class variable shared by all instances
def __init__(self, name):
self.name = name # instance variable unique to each instance
d = Dog('Fido')
e = Dog('Buddy')
d.kind # shared by all dogs
'canine'
e.kind # shared by all dogs
'canine'
d.name # unique to d
'Fido'
e.name # unique to e
'Buddy'
</pre>
正如在 術語相關 討論的, 可變 對象七问,例如列表和字典蜓耻,的共享數(shù)據(jù)可能帶來意外的效果。例如械巡,下面代碼中的 tricks 列表不應該用作類變量刹淌,因為所有的 Dog 實例將共享同一個列表:
<pre>class Dog:
tricks = [] # mistaken use of a class variable
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')
d.tricks # unexpectedly shared by all dogs
['roll over', 'play dead']
</pre>
這個類的正確設計應該使用一個實例變量:
<pre>class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # creates a new empty list for each dog
def add_trick(self, trick):
self.tricks.append(trick)
d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')
d.tricks
['roll over']
e.tricks
['play dead']
</pre>
備注
數(shù)據(jù)屬性會覆蓋同名的方法屬性饶氏。為了避免意外的名稱沖突,這在大型程序中是極難發(fā)現(xiàn)的 Bug有勾,使用一些約定來減少沖突的機會是明智的疹启。可能的約定包括:大寫方法名稱的首字母蔼卡,使用一個唯一的小字符串(也許只是一個下劃線)作為數(shù)據(jù)屬性名稱的前綴皮仁,或者方法使用動詞而數(shù)據(jù)屬性使用名詞。
數(shù)據(jù)屬性可以被方法引用菲宴,也可以由一個對象的普通用戶(客戶)使用贷祈。換句話說,類不能用來實現(xiàn)純凈的數(shù)據(jù)類型喝峦。事實上势誊,Python 中不可能強制隱藏數(shù)據(jù)——一切基于約定(如果需要,使用 C 編寫的 Python 實現(xiàn)可以完全隱藏實現(xiàn)細節(jié)并控制對象的訪問谣蠢。這可以用來通過 C 語言擴展 Python)粟耻。
客戶應該謹慎的使用數(shù)據(jù)屬性——客戶可能通過踐踏他們的數(shù)據(jù)屬性而使那些由方法維護的常量變得混亂。注意:只要能避免沖突眉踱,客戶可以向一個實例對象添加他們自己的數(shù)據(jù)屬性挤忙,而不會影響方法的正確性——再次強調,命名約定可以避免很多麻煩谈喳。
從方法內部引用數(shù)據(jù)屬性(或其他方法)并沒有快捷方式册烈。我覺得這實際上增加了方法的可讀性:當瀏覽一個方法時,在局部變量和實例變量之間不會出現(xiàn)令人費解的情況婿禽。
一般赏僧,方法的第一個參數(shù)被命名為 self
。這僅僅是一個約定:對 Python 而言扭倾,名稱 self
絕對沒有任何特殊含義淀零。(但是請注意:如果不遵循這個約定,對其他的 Python 程序員而言你的代碼可讀性就會變差膛壹,而且有些 類查看器 程序也可能是遵循此約定編寫的驾中。)
類屬性的任何函數(shù)對象都為那個類的實例定義了一個方法。函數(shù)定義代碼不一定非得定義在類中:也可以將一個函數(shù)對象賦值給類中的一個局部變量模聋。例如:
<pre># Function defined outside the class
def f1(self, x, y):
return min(x, x+y)
class C:
f = f1
def g(self):
return 'hello world'
h = g
</pre>
現(xiàn)在 f
肩民, g
和 h
都是類 C
的屬性,引用的都是函數(shù)對象撬槽,因此它們都是 C
實例的方法-- h
嚴格等于 g
此改。要注意的是這種習慣通常只會迷惑程序的讀者。
通過 self
參數(shù)的方法屬性侄柔,方法可以調用其它的方法:
<pre>class Bag:
def init(self):
self.data = []
def add(self, x):
self.data.append(x)
def addtwice(self, x):
self.add(x)
self.add(x)
</pre>
方法可以像引用普通的函數(shù)那樣引用全局命名共啃。與方法關聯(lián)的全局作用域是包含類定義的模塊。(類本身永遠不會做為全局作用域使用暂题。)盡管很少有好的理由在方法 中使用全局數(shù)據(jù)移剪,全局作用域確有很多合法的用途:其一是方法可以調用導入全局作用域的函數(shù)和方法,也可以調用定義在其中的類和函數(shù)薪者。通常纵苛,包含此方法的類也會定義在這個全局作用域,在下一節(jié)我們會了解為何一個方法要引用自己的類言津。
每個值都是一個對象攻人,因此每個值都有一個 類( class ) (也稱為它的 類型( type ) ),它存儲為 object.__class__
悬槽。
繼承
派生類的定義如下所示:
#!python
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
基類BaseClassName 必須與派生類定義在一個作用域內怀吻。還可以用表達式指定基類:
#!python
class DerivedClassName(modname.BaseClassName):
派生類定義的執(zhí)行和基類是一樣的。構造派生類對象時初婆,就記住了基類蓬坡。如果在類中找不到請求調用的屬性,就搜索基類磅叛。如果基類是由別的類派生而來屑咳,繼續(xù)招上一級。
派生類的實例化沒有什么特殊之處:DerivedClassName()創(chuàng)建新的類實例弊琴。方法引用按如下規(guī)則解析:搜索對應的類屬性兆龙,必要時沿基類鏈逐級搜索,如果找到了函數(shù)對象敲董,這個方法引用就是合法的详瑞。
派生類可能會重載基類的方法(對于 C++ 程序員來說,Python 中的所有方法本質上都是virtual方法)
直接調用基類方法臣缀,BaseClassName.methodname(self, arguments)坝橡。
Python 有兩個用于繼承的內置函數(shù):
函數(shù) isinstance() 用于檢查實例類型: isinstance(obj, int)只有在
obj.__class__
是 int 或其它從 int 繼承的類型的時候為True。函數(shù) issubclass() 用于檢查類繼承:issubclass(bool, int)為 True 精置,因為 bool 是 int 的子類计寇。 然而,issubclass(float, int) 為 False脂倦,因為 float 不是 int 的子類番宁。
多繼承
Python 同樣有限的支持多繼承形式。多繼承的類定義形如下例:
#!python
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
在大多數(shù)簡單情況下,搜索屬性深度優(yōu)先赖阻,左到右蝶押,而不是搜索兩次在同一個類層次結構中,其中有一個重疊火欧。因此棋电,如果在 DerivedClassName中沒有找到某個屬性茎截,就會搜索Base1,然后遞歸的搜索其基類赶盔,如果最終沒有找到企锌,就搜索 Base2,以此類推于未。
實際上撕攒,super() 可以動態(tài)的改變解析順序黑忱。類似其它多繼承語言的 call-next-method遭庶,比單繼承語言中的 super 更強大 镊绪。
動態(tài)調整順序十分必要的树灶,因為所有的多繼承會有一到多個菱形關系(指有至少一個祖先類可以從子類經(jīng)由多個繼承路徑到達)喊式。例如猫态,所有的 new-style 類繼承自 object 橄杨,所以任意的多繼承總是會有多于一條繼承路徑到達 object 莺葫。為了防止重復訪問基類片习,通過動態(tài)的線性化算法捌肴,每個類都按從左到右的順序特別指定了順序,每個祖先類只調用一次藕咏,這是單調的(意味著類被繼承時不會影響它祖先的次序)状知。這種方式使得設計可靠并且可擴展的多繼承類成為可能。更多的內容請參見 http://www.python.org/download/releases/2.3/mro/ 孽查。
私有變量
Python以下劃線開頭的命名(例如_spam)會被處理為 API 的非公開部分(無論它是函數(shù)饥悴、方法或數(shù)據(jù)成員)。
__spam(前面至少兩個下劃線盲再,后面至多一個)西设,被替代為_classname__spam 。
名稱重整是有助于子類重寫方法答朋,而不會打破組內的方法調用贷揽。例如:
#!python
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # private copy of original update() method
class MappingSubclass(Mapping):
def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)
名稱重整盡可能的避免沖突,私有的變量仍可訪問或修改梦碗。在特定的場合也是有用的禽绪,比如調試。
要注意傳入exec()或eval()的代碼時不考慮所調用的類的類名洪规,視其為當前類印屁,這類似于global 語句,已經(jīng)按字節(jié)編譯的部分也有同樣的限制斩例。這也同樣作用于getattr(), setattr()和delattr(), 像直接引用dict一樣雄人。
其他
類可以實現(xiàn)Pascal 中record或 C 中struct的數(shù)據(jù)類型,它將一組已命名的數(shù)據(jù)項綁定在一起念赶。一個空的類定義可以很好的實現(xiàn)它:
#!python
class Employee:
pass
john = Employee() # Create an empty employee record
# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
實例方法對象也有屬性:m.self是帶m()的實例對象础钠,而m.func是這個方法對應的函數(shù)對象恰力。
迭代器
多數(shù)容器對象都可以用 for 遍歷:
#!python
for element in [1, 2, 3]:
print(element)
for element in (1, 2, 3):
print(element)
for key in {'one':1, 'two':2}:
print(key)
for char in "123":
print(char)
for line in open("myfile.txt"):
print(line, end='')
這種風格的訪問清晰、簡潔珍坊、方便牺勾。迭代器的用法在 Python 中普遍而且統(tǒng)一正罢。 for 語句在容器對象中調用 iter() 阵漏。該函數(shù)返回定義了 next() 方法的迭代器對象,它在容器中逐一訪問元素翻具。沒有后續(xù)的元素時履怯, next() 拋出 StopIteration 異常通知 for 語句循環(huán)結束。你可以是用內建的 next() 函數(shù)調用 next() 方法裆泳;示例:
#!python
>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
next(it)
StopIteration
了解了迭代器協(xié)議的機制叹洲,就可以很容易的給自己的類添加迭代器行為。定義iter() 方法工禾,使其返回帶有 next() 方法的對象运提。如果這個類已經(jīng)定義了 next() ,那么 iter() 只需要返回 self:
#!python
class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
... print(char)
...
m
a
p
s
生成器
Generator 是創(chuàng)建迭代器的簡單而強大的工具闻葵。它們像函數(shù)民泵,但使用 yield 語句返回數(shù)據(jù)。每次調用 next() 槽畔,生成器從上次停頓的地方開始執(zhí)行(它記錄了最后一次執(zhí)行語句和所有的數(shù)據(jù)值)栈妆。
#!python
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
>>> for char in reverse('golf'):
... print(char)
...
f
l
o
g
基于類的迭代器能作的生成器也能作到。因為自動創(chuàng)建了 iter() 和 next() 方法厢钧,生成器更簡潔鳞尔。
另一個關鍵的功能在于兩次執(zhí)行之間,局部變量和執(zhí)行狀態(tài)都自動的保存下來早直。這使函數(shù)很容易寫寥假,而且比使用self.index和self.data之類實例變量的方式更清晰。
除了創(chuàng)建和保存程序狀態(tài)的自動方法霞扬,當發(fā)生器終止時糕韧,還會自動拋出 StopIteration 異常。
生成器表達式
簡單的生成器和列表推導式類似祥得。
#!python
>>> sum(i*i for i in range(10)) # sum of squares
285
>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec)) # dot product
260
>>> from math import pi, sin
>>> sine_table = {x: sin(x*pi/180) for x in range(0, 91)}
>>> unique_words = set(word for line in page for word in line.split())
>>> valedictorian = max((student.gpa, student.name) for student in graduates)
>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']