::-- limodou [2009-11-25 01:21:13]
[翻譯]Python Programming FAQ
by liqust at gmail dot com http://www.2pole.com/ -- 09/15/2005
此FAQ英文原版在http://www.python.org/doc/faq/programming.html
是的。
pdb模塊是一個簡單卻強(qiáng)大的命令行模式的python調(diào)試器。它是標(biāo)準(zhǔn)python庫的一部分, 在庫參考手冊中有關(guān)于它的文檔。作為一個例 子,你也可以使用pdb的代碼編寫你自己的調(diào)試器。
作為標(biāo)準(zhǔn)python發(fā)行包的一部分(通常為Tools/scripts/idle),IDLE包含了一個圖形界面的調(diào)試器。在http://www.python.org/idle/doc/idle2.html#Debugger有IDLE調(diào)試器的文檔。
另一個Python IDE,PythonWin包含了一個基于pdb的GUI調(diào)試器。 Pythonwin調(diào)試器對breakpoints作顏色標(biāo)記,它還有一些很酷的特性,比如調(diào)試非python程序。可以參考http://www.python.org/windows/pythonwin/。最 新版本的PythonWin已作為ActivePython 發(fā)行包的一部分(見 http://www.activestate.com/Products/ActivePython/index.html)。
Boa Constructor 是一個使用wxPython的IDE和GUI builder。它提供了可視化的框架創(chuàng)建和操作,一個對象探查器,多種代碼視圖比如對象瀏覽器,繼承架構(gòu),doc string創(chuàng)建的html文檔,一個高級調(diào)試器,繼承幫助和Zope支持。
Eric3 是基于PyQt和Scintilla editing組件的一個IDE。
Pydb是python標(biāo)準(zhǔn)調(diào)試器pdb的一個版本, 與DDD(Data Display Debugger, 一個流行的調(diào)試器圖形界面)一起工作。Pydb 可以在http://packages.debian.org/unstable/devel/pydb.html找到,DDD可以在 http://www.gnu.org/software/ddd找到.
還有很多包含圖形界面的商業(yè)版本Python IDE。包括:
Wing IDE (http://wingide.com)
Komodo IDE (http://www.activestate.com/Products/Komodo)
是的.
PyChecker是一個靜態(tài)分析器,它可以找出python代碼中的bug并對代碼的復(fù)雜性和風(fēng)格作出警告。可以在 http://pychecker.sf.net找到它.
另一個工具Pylint 檢查一個模塊是否滿足編碼規(guī)范,它還支持插件擴(kuò)展。除了PyChecker能提供的bug檢查外,Pylint 還提供額外的特性比如檢查代碼行長度,變量名是否符合代碼規(guī)范,聲明的接口是否都已實(shí)現(xiàn)等。http://www.logilab.org/projects/pylint/documentation 提供了關(guān)于Pylint特性的一個完整列表。
如果你只是希望用戶運(yùn)行一個單獨(dú)的程序而不需要預(yù)先下載一個python的發(fā)行版,則并不需要將Python代碼編譯成C代碼。有很多工具可以找出程序依賴的模塊并將這些模塊 與程序綁定在一起以產(chǎn)生一個單獨(dú)的執(zhí)行文件。
其中一種工具就是freeze tool, 它作為Tools/freeze被包含在python的代碼樹中。它將python字節(jié)碼轉(zhuǎn)換成C數(shù)組,和一個可將你所有模塊嵌入到新程序中的編譯器,這個編譯器跟python模塊鏈接在一起。
它根據(jù)import語句遞歸地掃描源代碼,并查找在標(biāo)準(zhǔn)python路徑中的模塊和源代碼目錄中的模塊(內(nèi)建模塊)。用python寫的模塊的字節(jié)碼隨后被轉(zhuǎn)換成C代碼(可以通過使用marshal模塊轉(zhuǎn)換成代碼對象的數(shù)組構(gòu) 造器),并產(chǎn)生一個可自定義的配置文件,只包含程序使用了的模塊。最后將生成的C代碼編譯并鏈接至余下的的python解釋器,產(chǎn)生一個與你的script執(zhí)行效果完全一樣的單獨(dú)文件。
顯然,freeze需要一個C編譯器。但也有一些工具并不需要。首先便是Gordon
它工作在Windows, Linux和至少是部分Unix變種上。
另一個便是Thomas Heller的 py2exe (只適用于Windows平臺),它在
第三個是Christian Tismer的 SQFREEZE,它將字節(jié)碼附在一個特殊的python解釋器后面,解釋器負(fù)責(zé)找到這段代碼。Python 2.4可能會引入類似的機(jī)制。
其它工具包括Fredrik Lundh的 Squeeze 和 Anthony Tuininga的 cx_Freeze.
是的。標(biāo)準(zhǔn)庫模塊要求的代碼風(fēng)格被列在PEP 8.
一般來說這是個復(fù)雜的問題。有很多技巧可以提升python的速度,比如可以用C重寫部分代碼。
在某些情況下將python轉(zhuǎn)換成C或x86匯編語言是可能的,這意味著您不需要修改代碼就可獲得速度提升。
Pyrex 可以將稍許改動過的python碼轉(zhuǎn)換成C擴(kuò)展,并可以在很多平臺上使用。
Psyco 是一個即時編譯器,可將python碼轉(zhuǎn)換成x86匯編語言。如果你可以使用它, Psyco 可使關(guān)鍵函數(shù)有明顯的性能提升。
剩下的問題就是討論各種可稍許提升python代碼速度的技巧。在profile指出某個函數(shù)是一個經(jīng)常執(zhí)行的熱點(diǎn)后,除非確實(shí)需要,否則不要應(yīng)用任何優(yōu)化措施,優(yōu)化經(jīng)常會使代碼變得不清晰,您不應(yīng)該承受這樣做所帶來的負(fù)擔(dān)(延長的開發(fā)時間,更多可能的bug),除非優(yōu)化結(jié)果確實(shí)值得你這樣做。
Skip Montanaro有一個專門關(guān)于提升python代碼速度的網(wǎng)頁,位于 http://manatee.mojam.com/~skip/python/fastpython.html。
Guido van Rossum 寫了關(guān)于提升python代碼速度的內(nèi)容,在http://www.python.org/doc/essays/list2str.html。
還有件需要注意的事,那就是函數(shù)特別是方法的調(diào)用代價相當(dāng)大;如果你設(shè)計了一個有很多小型函數(shù)的純面向?qū)ο蟮慕涌冢@些函數(shù)所做的不過是對實(shí)例變量獲取或賦值,又或是調(diào)用另一個方法,那么你應(yīng)該考慮使用更直接的方式比如直接存取實(shí)例變量。也可參照profile模塊(在Library Reference manual中描述),它 能找出程序哪些部分耗費(fèi)多數(shù)時間(如果你有耐性的話--profile本身會使程序數(shù)量級地變慢)。
記住很多從其它語言中學(xué)到的標(biāo)準(zhǔn)優(yōu)化方法也可用于python編程。比如,在執(zhí)行輸出時通過使用更大塊的寫入來減少系統(tǒng)調(diào)用會加快程序速度。因此CGI腳本一次性的寫入所有輸出就會比寫入很多次小塊輸出快得多。
同樣的,在適當(dāng)?shù)那闆r下使用python的核心特性。比如,通過使用高度優(yōu)化的C實(shí)現(xiàn),slicing允許程序在解釋器主循環(huán)的一個滴答中,切割list和其它sequence對象。因此 ,為取得同樣效果,為取得以下代碼的效果
1 2 L2 = [] 3 for i in range[3]: 4 L2.append(L1[i])
使用
1 2 L2 = list(L1[:3]) # "list" is redundant if L1 is a list.
則更短且快得多。
注意,內(nèi)建函數(shù)如map(), zip(), 和friends在執(zhí)行一個單獨(dú)循環(huán)的任務(wù)時,可被作為一個方便的加速器。比如將兩個list配成一對:
1 2 >>> zip([1,2,3], [4,5,6]) 3 [(1, 4), (2, 5), (3, 6)]
或在執(zhí)行一系列正弦值時:
1 2 >>> map(math.sin, (1,2,3,4)) 3 [0.841470984808, 0.909297426826, 0.14112000806, -0.756802495308]
在這些情況下,操作速度會很快。
其它的例子包括string對象的join()和split()方法。例如,如果s1..s7是大字符串(10K+)那么join([s1,s2,s3,s4,s5,s6,s7])就會比s1+s2+s3+s4+s5+s6+s7快得多,因?yàn)楹笳邥嬎愫芏啻巫颖磉_(dá)式,而join()則在一次過程中完成所有的復(fù)制。對于字符串操作,對字符串對象使用replace()方法。僅當(dāng)在沒有固定字符串模式時才使用正則表達(dá)式。考慮使用字符串格式化操作string % tuple和string % dictionary。
使用內(nèi)建方法list.sort()來排序,參考sorting mini-HOWTO中關(guān)于較高級的使用例子。除非在極特殊的情況下,list.sort()比其它任何 方式都要好。
另一個技巧就是"將循環(huán)放入函數(shù)或方法中" 。例如,假設(shè)你有個運(yùn)行的很慢的程序,而且你使用profiler確定函數(shù)ff()占用了很多時間。如果你注意到ff():
def ff(x):
常常是在循環(huán)中被調(diào)用,如:
1 2 list = map(ff, oldlist)
或:
1 2 for x in sequence: 3 value = ff(x) 4 ...do something with value...
那么你可以通過重寫ff()來消除函數(shù)的調(diào)用開銷:
1 2 def ffseq(seq): 3 resultseq = [] 4 for x in seq: 5 ...do something with x computing result... 6 resultseq.append(result) 7 return resultseq
并重寫以上兩個例子:
list = ffseq(oldlist)
和
1 2 for value in ffseq(sequence): 3 ...do something with value...
單獨(dú)對ff(x)的調(diào)用被翻譯成ffseq([x])[0],幾乎沒有額外開銷。當(dāng)然這個技術(shù)并不總是合適的,還是其它的方法。
你可以通過將函數(shù)或方法的定位結(jié)果精確地存儲至一個本地變量來獲得一些性能提升。一個循環(huán)如:
1 2 for key in token: 3 dict[key] = dict.get(key, 0) + 1
每次循環(huán)都要定位dict.get。如果這個方法一直不變,可這樣實(shí)現(xiàn)以獲取小小的性能提升:
1 2 dict_get = dict.get # look up the method once 3 for key in token: 4 dict[key] = dict_get(key, 0) + 1
默認(rèn)參數(shù)可在編譯期被一次賦值,而不是在運(yùn)行期。這只適用于函數(shù)或?qū)ο笤诔绦驁?zhí)行期間不被改變的情況,比如替換
1 2 def degree_sin(deg): 3 return math.sin(deg * math.pi / 180.0)
為
1 2 def degree_sin(deg, factor = math.pi/180.0, sin = math.sin): 3 return sin(deg * factor)
因?yàn)檫@個技巧對常量變量使用了默認(rèn)參數(shù),因而需要保證傳遞給用戶API時不會產(chǎn)生混亂。
你是否做過類似的事?
1 2 x = 1 # make a global 3 4 def f(): 5 print x # try to print the global 6 ... 7 for j in range(100): 8 if q>3: 9 x=4
任何函數(shù)內(nèi)賦值的變量都是這個函數(shù)的local變量。除非它專門聲明為global。作為函數(shù)體最后一個語句,x被賦值,因此編譯器認(rèn)為x為local變量。而語句print x 試圖 print一個未初始化的local變量,因而會觸發(fā)NameError 異常。
解決辦法是在函數(shù)的開頭插入一個明確的global聲明。
1 2 def f(): 3 global x 4 print x # try to print the global 5 ... 6 for j in range(100): 7 if q>3: 8 x=4
在這種情況下,所有對x的引用都是模塊名稱空間中的x。
在Python中,某個變量在一個函數(shù)里只是被引用,則認(rèn)為這個變量是global。如果函數(shù)體中變量在某個地方會被賦值,則認(rèn)為這個變量是local。如果一個global變量在函數(shù)體中 被賦予新值,這個變量就會被認(rèn)為是local,除非你明確地指明其為global。
盡管有些驚訝,我們略微思考一下就會明白。一方面,對于被賦值的變量,用關(guān)鍵字 global是為了防止意想不到的邊界效應(yīng)。另一方面,如果對所有的global引用都需要關(guān)鍵字global,則會不停地使用global關(guān)鍵字。需要在每次引用內(nèi)建函數(shù)或一個import的模塊時都聲明global。global聲明是用來確定邊界效應(yīng)的,而這 種混亂的用法會抵消這個作用。
在一個單獨(dú)程序中,各模塊間共享信息的標(biāo)準(zhǔn)方法是創(chuàng)建一個特殊的模塊(常被命名為config和cfg)。僅需要在你程序中每個模塊里import這個config模塊。 因?yàn)槊總€模塊只有一個實(shí)例,對這個模塊的任何改變將會影響所有的地方。例如:
config.py:
1 2 x = 0 # Default value of the 'x' configuration setting
mod.py:
1 2 import config 3 config.x = 1
main.py:
1 2 import config 3 import mod 4 print config.x
注意,由于同樣的原因,使用模塊也是實(shí)現(xiàn)Singleton設(shè)計模式的基礎(chǔ)。
通常情況下,不要使用from modulename import * 這種格式。這樣做會使引入者的namespace混亂。很多人甚至對于那些專門設(shè)計用于這種模式的模塊都不采用這種方式。被設(shè)計成這種模式的模塊包括Tkinter, 和threading.
在一個文件的開頭引入模塊。這樣做使得你的你的代碼需要哪些模塊變得清晰,并且避免了模塊名稱是否存在的問題。 在每行只使用一次import使得添加和刪除模塊import更加容易,但每行多個import則減少屏幕空間的使用。
應(yīng)該按照以下順序import模塊:
第三方模塊(安裝在python的site-packages目錄下) -- 如 mx.DateTime, ZODB, PIL.Image, 等。
不要使用相對的import。如果你在編寫package.sub.m1 模塊的代碼并想 import package.sub.m2, 不要只是import m2, 即使這樣是合法的。用 from package.sub import m2 代替.相對的imports會導(dǎo)致模塊被初始化兩次,并產(chǎn)生奇怪的bug。
有時需要將import語句移到函數(shù)或類中來防止import循環(huán)。 Gordon McMillan 說:
在 兩個模塊都使用 "import <module>" 格式時是沒問題的 。但若第二個模塊想要獲取第一個模塊以外的一個名稱("from module import name")且這個import語句位于最頂層時,則會產(chǎn)生錯誤 。因?yàn)檫@時第一個模塊的名稱并不處于有效狀態(tài),因?yàn)榈谝粋€模塊正忙于import第二個模塊。
在這種情況下,如果第二個模塊只是用在一個函數(shù)中,那么可以簡單地把import移入到這個函數(shù)中。當(dāng)這個import被調(diào)用時,第一個模塊已經(jīng)完成了初始化,而第二個模塊 則可以完成它的import語句了。
如果某些模塊是系統(tǒng)相關(guān)的,那么將import移出頂層代碼也是必要的。在那種情況下,甚至不可能在文件的頂層import所有的模塊。在這種情況下,在對應(yīng)的系統(tǒng)相關(guān)代碼中引入這些模塊則是個好的選擇。
在解決諸如防止import循環(huán)或試圖減少模塊初始化時間等問題,且諸多模塊并不需要依賴程序是如何執(zhí)行的情況下,這種方法尤其有用。如果模塊只是被用在某個函數(shù)中,你也可以將import移到這個函數(shù)中。注意首次import模塊會花費(fèi)較多的時間,但多次地import則幾乎不會再花去額外的時間,而只是需要兩次的字典查詢操作。即使模塊名稱已經(jīng)處在scope外,這個模塊也很有可能 仍處在sys.modules中。
如果只是某個類的實(shí)例使用某個模塊,則應(yīng)該在類的__init__方法里import模塊并把這個模塊賦給一個實(shí)例變量以使這個模塊在對象的整個生命周期內(nèi)一直有效(通過這個實(shí)例變量)。注意要使import推遲到類的實(shí)例化,必須將import放入某個方法中。在類里所有方法之外的地方放置import語句,仍然會 使模塊初始化的時候執(zhí)行import。
在函數(shù)的參數(shù)列表中使用 * 和 ** ;它將你的位置參數(shù)作為一個tuple,將鍵值參數(shù)作為一個字典。當(dāng)調(diào)用另一個函數(shù)時你可以通過使用 * 和 **來傳遞這些參數(shù):
1 2 def f(x, *tup, **kwargs): 3 ... 4 kwargs['width']='14.3c' 5 ... 6 g(x, *tup, **kwargs)
如果考慮到比python的2.0更老的版本的特殊情況,使用'apply':
1 2 def f(x, *tup, **kwargs): 3 ... 4 kwargs['width']='14.3c' 5 ... 6 apply(g, (x,)+tup, kwargs)
記住在python中參數(shù)傳遞是動過賦值實(shí)現(xiàn)的。因?yàn)橘x值僅是創(chuàng)建一個新的對對象的引用,所以在調(diào)用者和被調(diào)用者之間沒有任何的別名可以使用,因此從本質(zhì)上說沒有傳引用調(diào)用。但你可以通過一系列的方法來實(shí)現(xiàn)這個效果。
1 2 def func2(a, b): 3 a = 'new-value' # a and b are local names 4 b = b + 1 # assigned to new objects 5 return a, b # return new values 6 7 x, y = 'old-value', 99 8 x, y = func2(x, y) 9 print x, y # output: new-value 100
1 2 def func1(a): 3 a[0] = 'new-value' # 'a' references a mutable list 4 a[1] = a[1] + 1 # changes a shared object 5 6 args = ['old-value', 99] 7 func1(args) 8 print args[0], args[1] # output: new-value 100
1 2 def func3(args): 3 args['a'] = 'new-value' # args is a mutable dictionary 4 args['b'] = args['b'] + 1 # change it in-place 5 6 args = {'a':' old-value', 'b': 99} 7 func3(args) 8 print args['a'], args['b']
1 2 class callByRef: 3 def __init__(self, **args): 4 for (key, value) in args.items(): 5 setattr(self, key, value) 6 7 def func4(args): 8 args.a = 'new-value' # args is a mutable callByRef 9 args.b = args.b + 1 # change object in-place 10 11 args = callByRef(a='old-value', b=99) 12 func4(args) 13 print args.a, args.b
最好的方法還是返回一個包含多個結(jié)果的tuple。
有兩個選擇:你可以使用內(nèi)嵌的方式或使用可調(diào)用對象。比如,假設(shè)你想定義 linear(a,b),
它返回計算a*x+b 的函數(shù)f(x)。使用內(nèi)嵌的方法:
1 2 def linear(a,b): 3 def result(x): 4 return a*x + b 5 return result
或者使用可調(diào)用的類:
1 2 class linear: 3 def __init__(self, a, b): 4 self.a, self.b = a,b 5 def __call__(self, x): 6 return self.a * x + self.b
兩種方法都是:
1 2 taxes = linear(0.3,2)
給出一個可調(diào)用對象,taxes(10e6) 0.3 * 10e6 + 2。
用可調(diào)用對象的方法有個缺點(diǎn),那就是這樣做會慢一些且代碼也會長一些。但是,注意到一系列的可調(diào)用對象可通過繼承共享信號。
1 2 class exponential(linear): 3 # __init__ inherited 4 def __call__(self, x): 5 return self.a * (x ** self.b)
對象可以對若干方法封裝狀態(tài)信息:
1 2 class counter: 3 value = 0 4 def set(self, x): self.value = x 5 def up(self): self.value=self.value+1 6 def down(self): self.value=self.value-1 7 8 count = counter() 9 inc, dec, reset = count.up, count.down, count.set
這里inc(), dec() 和 reset() 運(yùn)性起來就像是一組共享相同計數(shù)變量的函數(shù)。
通常,使用copy.copy() 或 copy.deepcopy()。并不是所有的對象都可以被復(fù)制,但大多數(shù)是可以的。
某些對象可以被簡單地多的方法復(fù)制。字典有個copy() 方法:
1 2 newdict = olddict.copy()
序列可以通過slicing來復(fù)制:
1 2 new_l = l[:]
對于一個用戶定義的類的實(shí)例x,dir(x) 返回一個按字母排序的列表,其中包含了這個實(shí)例的屬性和方法,類的屬性。
一般來說是不行的,因?yàn)閷?shí)際上對象并沒有名稱。實(shí)質(zhì)上,賦值經(jīng)常將一個名稱綁定到一個值;對于def 和 class 語句也是一樣, 但在那種情況下這個變量是可調(diào)用的。考慮以下代碼:
1 2 class A: 3 pass 4 5 B = A 6 7 a = B() 8 b = a 9 print b 10 <__main__.A instance at 016D07CC> 11 print a 12 <__main__.A instance at 016D07CC>
理論上這個類有名稱:盡管它被綁定到兩個名稱,通過名稱B進(jìn)行調(diào)用,這個新創(chuàng)建的實(shí)例仍然被作為是類A的實(shí)例。但是,因?yàn)閮蓚€名稱都被綁定到同樣的值,因此說這個實(shí)例的名稱到底是A還是B是不可能的。
一般來說,讓你的代碼知道特定對象的名稱并不是必要的。除非去特意地編寫一個自省程序,否則這往往意味著需要改變一下代碼。
在comp.lang.python, Fredrik Lundh 曾經(jīng)給出了一個極好的解答:
沒有。在很多情況下你可以用"a and b or c"模擬 a?b:c with , 但這樣做有個缺陷:如果b是zero(或 empty, 或None -- 只要為false)則c被選擇。在很多情況下你可以查看代碼以保證這種情況不會發(fā)生(例如,因?yàn)閎是個常數(shù)或是一種永遠(yuǎn)不會為false的類型),但是通常來書它的確是個問題。
TimPeters (本人希望是Steve Majewski) 有以下建議: (a and [b] or [c])[0]. 因?yàn)?[b]是一個永遠(yuǎn)不會為false的列表,所以錯誤的情況不會發(fā)生;然后對整個表達(dá)式使用 [0]來得到想要的b或者c。很難看,但在你重寫代碼并且使用'if'很不方便的情況下,這種方式是有效的。
最好的方式還是用 if...else 語句。另一種方法就是用一個函數(shù)來實(shí)現(xiàn) "?:" 操作符:
1 2 def q(cond,on_true,on_false): 3 if cond: 4 if not isfunction(on_true): return on_true 5 else: return apply(on_true) 6 else: 7 if not isfunction(on_false): return on_false 8 else: return apply(on_false)
在大多數(shù)情況下,你會直接傳遞b和c: q(a,b,c)。為防止在不合適的情況下計算 b 或者 c,用一個lambda函數(shù)封裝它們,例如:q(a,lambda: b, lambda: c)。
為什么python沒有if-then-else表達(dá)式。有幾個回答:很多語言在沒有這個的情況下也工作得很好;它會減少可讀代碼的數(shù)量;還沒有足夠多的python風(fēng)格的語法;通過對標(biāo)準(zhǔn)庫的搜索,發(fā)現(xiàn)幾乎沒有這種情況:通過使用 if-then-else 表達(dá)式讓代碼的可讀性更好。
在 2002年, PEP 308 提交了若干語法建議,整個社區(qū)對此進(jìn)行了一次非決定性的投票。很多人喜歡某個語法而反對另外的語法;投票結(jié)果表明,很多人寧愿沒有三元操作符,也不愿意創(chuàng)建一種新的令人討厭的語法,。
是的。這經(jīng)常發(fā)生在將lambda嵌入到lambda的情況,根據(jù) Ulf Bartelt,有以下三個例子:
1 2 # Primes < 1000 3 print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0, 4 map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000))) 5 6 # First 10 Fibonacci numbers 7 print map(lambda x,f=lambda x,f:(x<=1) or (f(x-1,f)+f(x-2,f)): f(x,f), 8 range(10)) 9 10 # Mandelbrot set 11 print (lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+y,map(lambda y, 12 Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM, 13 Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro, 14 i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y 15 >=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr( 16 64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy 17 ))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24) 18 # \___ ___ \___ ___ | | |__ lines on screen 19 # V V | |______ columns on screen 20 # | | |__________ maximum of "iterations" 21 # | |_________________ range on y axis 22 # |____________________________ range on x axis
小朋友不要在家里嘗試這個!
要指定一個八進(jìn)制數(shù)字,在八進(jìn)制之前加個0。例如,將a設(shè)置成八進(jìn)制的10,輸入:
1 2 >>> a = 010 3 >>> a 4 8
十六進(jìn)制也很簡單。在十六進(jìn)制前加個0x。十六進(jìn)制數(shù)可以大寫也可以小寫。比如,在python解釋器中:
1 2 >>> a = 0xa5 3 >>> a 4 165 5 >>> b = 0XB2 6 >>> b 7 178
這是因?yàn)?i%j 跟 j 為同樣類型。如果你想那樣,且又想:
1 2 i (i/j)*j + (i%j)
那么整數(shù)除法就必須返回一個浮點(diǎn)值。C也有這個要求,編譯器截斷i/j,并使i的類型和i%j一樣。
在實(shí)際應(yīng)用中, i%j 的j是負(fù)數(shù)的可能性很小。當(dāng)j是正數(shù)時,多數(shù)情況(實(shí)際上是所有情況)下i%j >= 0是很有用的。如果現(xiàn)在是10點(diǎn),那么200小時以前是多少? -190 % 12 2 是正確的,而-190 % 12 -10 則是個bug。
對于整數(shù),使用內(nèi)建的 int() 類型構(gòu)造器, 例如 int('144') 144。類似的,float() 轉(zhuǎn)換成浮點(diǎn)數(shù),例如 float('144') 144.0。
默認(rèn)的,這些數(shù)字被解釋成十進(jìn)制,所以 int('0144') 144 而 int('0x144') 則拋出ValueError異常。 int(string, base) 提供了第二個參數(shù)來指定類型,所以int('0x144', 16) 324。
如果base被指定為0,則會按python的規(guī)則來解釋:開頭為一個 '0' 表示八進(jìn)制,而 '0x' 表示十六進(jìn)制。
如果你只是將字符串轉(zhuǎn)換成數(shù)字,不用使用內(nèi)建函數(shù) eval()。eval()會慢很多并 有安全風(fēng)險:某人傳遞給python一個表達(dá)式,可能會有意想不到的邊界效應(yīng)。例如,某人傳遞 __import__('os').system("rm -rf $HOME") 會清除掉你的home目錄。
eval()
也能將數(shù)字解釋成為python表達(dá)式,所以 eval('09') 會給出一個語法錯誤,因?yàn)閜ython將開頭為'0'的數(shù)字認(rèn)為是八進(jìn)制(base 8)。
比如,數(shù)字144轉(zhuǎn)換成字符串 '144', 使用內(nèi)建函數(shù) str()。 如果想用八進(jìn)制或十六進(jìn)制表示,使用內(nèi)建函數(shù)hex() 或oct()。對于格式化,使用 [../../doc/lib/typesseq-strings.html % operator],比如,"%04d" % 144 為 '0144' , "%.3f" % (1/3.0) 為 '0.333'。更多細(xì)節(jié)查看庫參考手冊。
不能,因?yàn)樽址遣豢筛牡摹H绻愦_實(shí)需要一個具有這個能力的對象,將字符串轉(zhuǎn)換成列表或使用數(shù)組模塊:
1 2 >>> s = "Hello, world" 3 >>> a = list(s) 4 >>> print a 5 ['H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd'] 6 >>> a[7:] = list("there!") 7 >>> ''.join(a) 8 'Hello, there!' 9 10 >>> import array 11 >>> a = array.array('c', s) 12 >>> print a 13 array('c', 'Hello, world') 14 >>> a[0] = 'y' ; print a 15 array('c', 'yello world') 16 >>> a.tostring() 17 'yello, world'
這里有幾種方式:
1 2 def a(): 3 pass 4 5 def b(): 6 pass 7 8 dispatch = {'go': a, 'stop': b} # Note lack of parens for funcs 9 10 dispatch[get_input()]() # Note trailing parens to call function
1 2 def myFunc(): 3 print "hello" 4 5 fname = "myFunc" 6 7 f = locals()[fname] 8 f() 9 10 f = eval(fname) 11 f()
從Python 2.2起,可以使用S.rstrip("\r\n") 來去除字符串S結(jié)尾處的任何行符,而不會去除結(jié)尾處的其它空白符。如果字符串S并不只是一行,并在末尾有若干個空行,所有空行的行符都會被去除:
1 2 >>> lines = ("line 1 \r\n" 3 ... "\r\n" 4 ... "\r\n") 5 >>> lines.rstrip("\n\r") 6 "line 1 "
當(dāng)程序每次只能讀取一行數(shù)據(jù)時,這樣使用S.rstrip() 是很合適的。
對于老版本的Python, 分別有兩個替代方法:
沒有。
對于簡單的輸入分析,最簡單的方法就是用string對象的 split() 將輸入行分割成若干用空格分割的單詞,然后用 int() 或float()將數(shù)字字符串轉(zhuǎn)換成數(shù)字。split() 支持可選參數(shù) "sep" 用來處理分隔符不是空格的情況。
對于更復(fù)雜的輸入分析,正則表達(dá)式比C的sscanf() 更強(qiáng)大和更合適。
這個錯誤表明python只能處理 7-bit 的 ASCII 字符串。這里有幾種方法可以解決這個問題。
如果程序需要處理任意編碼的數(shù)據(jù),程序的運(yùn)行環(huán)境一般都會指定它傳給你的數(shù)據(jù)的編碼。你需要用那個編碼將輸入數(shù)據(jù)轉(zhuǎn)換成 Unicode數(shù)據(jù)。例如,一個處理 email 或web輸入的程序會在 Content-Type頭里發(fā)現(xiàn)字符編碼信息。在稍后將數(shù)據(jù)轉(zhuǎn)換成Unicode時會使用到這個信息。假設(shè)通過 value 引用的字符串的編碼為 UTF-8:
1 2 value = unicode(value, "utf-8")
會返回一個 Unicode 對象。如果數(shù)據(jù)沒有被正確地用 UTF-8 編碼,那么這個調(diào)用會觸發(fā)一個 UnicodeError 異常。
如果你只是想把非 ASCII 的數(shù)據(jù)轉(zhuǎn)換成 Unicode,你可以首先假定為 ASCII 編碼,如果失敗再產(chǎn)生 Unicode 對象。
1 2 try: 3 x = unicode(value, "ascii") 4 except UnicodeError: 5 value = unicode(value, "utf-8") 6 else: 7 # value was valid ASCII data 8 pass
可以在python庫中的一個叫sitecustomize.py 的文件中設(shè)定默認(rèn)編碼。但并不推薦這樣 ,因?yàn)楦淖冞@個全局值可能會導(dǎo)致第三方的擴(kuò)展模塊出錯。
注意,在 Windows 上有一種編碼為 "mbcs",它是根據(jù)你目前的locale使用編碼。在很多情況下,尤其是跟 COM 一起工作的情況下,這是個合適的默認(rèn)編碼。
函數(shù) tuple(seq) 將任何序列(實(shí)際上,任何可遍歷的對象)轉(zhuǎn)換成一個tuple,并有著同樣的元素和順序。
例如,tuple([1, 2, 3]) 得到 (1, 2, 3),而 tuple('abc') 得到 ('a', 'b', 'c')。如果參數(shù)就是一個 tuple, 則不做任何復(fù)制而返回相同的對象,所以當(dāng)你不確定一個對象是否是tuple時調(diào)用tuple()也沒有額外的開銷。
函數(shù) list(seq) 將任何序列或任何可遍歷的對象轉(zhuǎn)換成一個list,并有著同樣的元素和順序。比如 list((1, 2, 3)) 得到[1, 2, 3],而 list('abc') 得到 ['a', 'b', 'c']。 如果參數(shù)就是一個列表,則執(zhí)行一個復(fù)制操作,就像seq[:] 一樣。
Python序列的索引可正可負(fù)。若使用正索引,0是第一個索引,1是第二個索引,以此類推。若使用負(fù)索引,-1 表示最后一個索引,-2表示倒數(shù)第二個,以此類推。比如seq[-n] 與 seq[len(seq)-n] 相同。
使用負(fù)索引帶來很大的方便。比如 S-1 是除最后一個字符外的所有字符串,這在移除字符串結(jié)尾處的換行符時非常有用。
如果是一個列表, 最快的解決方法是
1 2 list.reverse() 3 try: 4 for x in list: 5 "do something with x" 6 finally: 7 list.reverse()
這樣做有個缺點(diǎn),就是當(dāng)你在循環(huán)時,這個list被臨時反轉(zhuǎn)了。如果不喜歡這樣,也可做一個復(fù)制。這樣雖然看起來代價較大,但實(shí)際上比其它方法要快。
1 2 rev = list[:] 3 rev.reverse() 4 for x in rev: 5 <do something with x>
如果它不是個列表,一個更普遍但也更慢的方法是:
1 2 for i in range(len(sequence)-1, -1, -1): 3 x = sequence[i] 4 <do something with x>
還有一個更優(yōu)雅的方法,就是定義一個類,使它像一個序列一樣運(yùn)行,并反向遍歷(根據(jù) Steve Majewski 的方法):
1 2 class Rev: 3 def __init__(self, seq): 4 self.forw = seq 5 def __len__(self): 6 return len(self.forw) 7 def __getitem__(self, i): 8 return self.forw[-(i + 1)]
你可以簡單地寫成:
1 2 for x in Rev(list): 3 <do something with x>
然而,由于方法調(diào)用的開銷,這是最慢的一種方法。
當(dāng)使用 Python 2.3時,你可以使用一種擴(kuò)展的slice語法:
1 2 for x in sequence[::-1]: 3 <do something with x>
Python Cookbook中有一個關(guān)于這個的較長的論述,提到了很多方法,參考:
如果你不介意重新排列這個列表,那么對它進(jìn)行排序并從list的末尾開始掃描,將重復(fù)元素刪掉:
1 2 if List: 3 List.sort() 4 last = List[-1] 5 for i in range(len(List)-2, -1, -1): 6 if lastList[i]: del List[i] 7 else: last=List[i]
如果列表中所有的元素都可用作字典的鍵值(即它們都是hashable),那么通常這樣更快:
1 2 d = {} 3 for x in List: d[x]=x 4 List = d.values()
使用列表:
1 2 ["this", 1, "is", "an", "array"]
列表對等于C或Pascal中的數(shù)組;最大的不同是python的列表可以包含很多不同的數(shù)據(jù)類型。
array 模塊也可以提供方法來創(chuàng)建緊湊表示的固定類型數(shù)組,但是它的索引會比 列表慢。也要注意可定義類似數(shù)組且擁有各種特性的Numeric擴(kuò)展和其它方式。
要獲得Lisp風(fēng)格鏈接的列表,你可以通過使用tuple來模擬cons cells。
1 2 lisp_list = ("like", ("this", ("example", None) ) )
如果需要在運(yùn)行時可更改,可以使用列表代替tuple。這里類似lisp car 的是 lisp_list[0] ,而類似 cdr 的是 lisp_list[1]。 僅當(dāng)你確定需要時使用它,因?yàn)檫@樣比使用python列表慢很多。
你很有可能用這種方式來產(chǎn)生一個多維數(shù)組:
1 2 A = [[None] * 2] * 3
如果你pirnt它的話似乎是正確的:
1 2 >>> A 3 [[None, None], [None, None], [None, None]]
但是當(dāng)你賦一個值時,它會出現(xiàn)在好幾個地方:
1 2 >>> A[0][0] = 5 3 >>> A 4 [[5, None], [5, None], [5, None]]
這是因?yàn)橛?* 來復(fù)制時,只是創(chuàng)建了對這個對象的引用,而不是真正的創(chuàng)建了它。 *3 創(chuàng)建了一個包含三個引用的列表,這三個引用都指向同一個長度為2的列表。其中一個行的改變會顯示在所有行中,這當(dāng)然不是你想要的。
建議創(chuàng)建一個特定長度的list,然后用新的list填充每個元素:
1 2 A = [None]*3 3 for i in range(3): 4 A[i] = [None] * 2
這樣創(chuàng)建了一個包含三個不同的長度為2的列表。你也可以使用list comprehension:
1 2 w,h = 2,3 3 A = [ [None]*w for i in range(h) ]
或者,你可以使用一個擴(kuò)展來提供矩陣數(shù)據(jù)類型;Numeric Python 是最有名的。
使用list comprehension:
1 2 result = [obj.method() for obj in List]
更一般的,可以使用以下函數(shù):
1 2 def method_map(objects, method, arguments): 3 """method_map([a,b], "meth", (1,2)) gives [a.meth(1,2), b.meth(1,2)]""" 4 nobjects = len(objects) 5 methods = map(getattr, objects, [method]*nobjects) 6 return map(apply, methods, [arguments]*nobjects)
不能這樣。字典按不可預(yù)測的順序存儲數(shù)據(jù),所以字典的顯示順序也是不可預(yù)測的。
或許你正想保存一個可打印版本到一個文件,做某些更改后將其與其它顯示的字典比較,這個回答會使你感到沮喪。在這種情況下,使用pprint模塊來pretty-print字典,這樣元素會按鍵值排序。
另一個復(fù)雜的多的方法就是繼承UserDict.UserDict 并創(chuàng)建類SortedDict ,以一個可預(yù)知的順序顯示出來。這里有一個示例:
1 2 import UserDict, string 3 4 class SortedDict(UserDict.UserDict): 5 def __repr__(self): 6 result = [] 7 append = result.append 8 keys = self.data.keys() 9 keys.sort() 10 for k in keys: 11 append("%s: %s" % (`k`, `self.data[k]`)) 12 return "{%s}" % string.join(result, ", ") 13 14 ___str__ = __repr__
雖然這不是個完美的解決方案,但它可以在你遇到的很多情況下工作良好,最大的缺陷就是,如果字典中某個值也是字典,那么將不會以任何特定順序顯示值。
可以,通過使用list comprehensions會非常簡單。
根據(jù)Perl社區(qū)的Randal Schwartz的方法,創(chuàng)建一個矩陣,這個矩陣將列表的每個元素都映射到相應(yīng)的“排序值”上,通過這個矩陣對列表進(jìn)行排序。有一個字符串列表,用字符串的大寫字母值排序:
1 2 tmp1 = [ (x.upper(), x) for x in L ] # Schwartzian transform 3 tmp1.sort() 4 Usorted = [ x[1] for x in tmp1 ]
對每個字符串的10-15位置的子域擴(kuò)展的整數(shù)值進(jìn)行排序:
1 2 tmp2 = [ (int(s[10:15]), s) for s in L ] # Schwartzian transform 3 tmp2.sort() 4 Isorted = [ x[1] for x in tmp2 ]
注意到 Isorted 也能被這樣計算:
1 2 def intfield(s): 3 return int(s[10:15]) 4 5 def Icmp(s1, s2): 6 return cmp(intfield(s1), intfield(s2)) 7 8 Isorted = L[:] 9 Isorted.sort(Icmp)
但是因?yàn)檫@個方法對L的每個元素調(diào)用intfield()多次,所以要比Schwartzian變換慢。
將它們合并成一個包含若干tuple的列表,對列表排序,然后選取你想要的元素。
1 2 >>> list1 = ["what", "I'm", "sorting", "by"] 3 >>> list2 = ["something", "else", "to", "sort"] 4 >>> pairs = zip(list1, list2) 5 >>> pairs 6 [('what', 'something'), ("I'm", 'else'), ('sorting', 'to'), ('by', 'sort')] 7 >>> pairs.sort() 8 >>> result = [ x[1] for x in pairs ] 9 >>> result 10 ['else', 'sort', 'to', 'something']
對于最后一步,一個替代方法是:
1 2 result = [] 3 for p in pairs: result.append(p[1])
如果你發(fā)現(xiàn)這樣做更清晰,你也許會使用這個替代方法。但是,對于長列表,它幾乎會花去大約兩倍的時間。為什么?首先,append()操作需要重新分配內(nèi)存,雖然它使用了一些技巧用來防止每次操作時都這樣做,它的消耗依然很大。第二,表達(dá)式 "result.append"需要一個額外的屬性定位,第三,所有的函數(shù)調(diào)用也會減慢速度。
類是在執(zhí)行類語句時創(chuàng)建的特殊對象。類對象被用來作為模板創(chuàng)建實(shí)例對象,它包含了針對某個數(shù)據(jù)類型的數(shù)據(jù)(屬性)和代碼(方法)。
一個類可以繼承一個或多個被稱為基類的類。其繼承了基類的屬性和方法。這就允許通過繼承來對類進(jìn)行重定義。假設(shè)有一個通用的Mailbox類提供基本的郵箱存取操作,那么它的子類比如 MboxMailbox, MaildirMailbox, OutlookMailbox 可以處理各種特定的郵箱格式。
method就是某個在類x中的函數(shù),一般調(diào)用格式為 x.name(arguments...)。 方法在類定義中被定義成函數(shù):
1 2 class C: 3 def meth (self, arg): 4 return arg*2 + self.attribute
Self僅僅是method的第一個常規(guī)參數(shù)。一個method定義為 meth(self, a, b, c),
對于定義這個method的類的某個實(shí)例x,調(diào)用時為 x.meth(a, b, c) ,而實(shí)際上是 meth(x, a, b, c)。
參考[../draft/general.html#why-must-self-be-used-explicitly-in-method-definitions-and-callsWhy must 'self' be used explicitly in method definitions and calls?]
使用內(nèi)建函數(shù) isinstance(obj, cls)。 你可以通過一個tuple來檢查某個對象是否是一系列類的實(shí)例,例如isinstance(obj, (class1, class2, ...)), 并檢查某個對象是否是python的內(nèi)建類型,例如isinstance(obj, str) or isinstance(obj, (int, long, float, complex))。
注意多數(shù)程序并不經(jīng)常使用 isinstance() 來檢查用戶定義的類,如果你自己在編寫某個類,一個更好的面向?qū)ο箫L(fēng)格的方法就是定義一個封裝特定功能的method,而不是檢查對象所屬的類然后根據(jù)這個來調(diào)用函數(shù)。例如,如果你有某個函數(shù):
1 2 def search (obj): 3 if isinstance(obj, Mailbox): 4 # ... code to search a mailbox 5 elif isinstance(obj, Document): 6 # ... code to search a document 7 elif ...
一個更好的方法就是對所有的類都定義一個search() 方法:
1 2 class Mailbox: 3 def search(self): 4 # ... code to search a mailbox 5 6 class Document: 7 def search(self): 8 # ... code to search a document 9 10 obj.search()
Delegation是一個面向?qū)ο蠹夹g(shù)(也被稱為一種設(shè)計模式)。假設(shè)你有個類x并想改變它的某個方法method。 你可以創(chuàng)建一個新類,提供這個method的一個全新實(shí)現(xiàn),然后將其它method都delegate到x中相應(yīng)的method。
Python程序員可以輕易地實(shí)現(xiàn)delegation。比如,下面這個類像一個文件一樣使用,但它將所有的數(shù)據(jù)都轉(zhuǎn)換成大寫:
1 2 class UpperOut: 3 def __init__(self, outfile): 4 self.__outfile = outfile 5 def write(self, s): 6 self.__outfile.write(s.upper()) 7 def __getattr__(self, name): 8 return getattr(self.__outfile, name)
在這里類UpperOut 重新定義了write() 方法,在調(diào)用self.outfile.write()方法之前,將字符串參數(shù) 都轉(zhuǎn)換成大寫。所有其它的method都delegate到self.outfile 相應(yīng)的method。這個delegation通過 __getattr__ 方法來完成;關(guān)于控制屬性存取的更多信息,參考 [../../doc/ref/attribute-access.html the language reference] 。
注意到更多的情況下,delegation使人產(chǎn)生疑惑。 若需要修改屬性,還需要在類中定義__settattr__ 方法,并應(yīng)小心操作。__setattr__ 的實(shí)現(xiàn)基本與以下一致:
1 2 class X: 3 ... 4 def __setattr__(self, name, value): 5 self.__dict__[name] = value 6 ...
大多數(shù)__setattr__實(shí)現(xiàn)必須修改self.__dict__,用來存儲自身的本地狀態(tài)信息以防止無限遞歸。
如果你使用的是新風(fēng)格的類,使用內(nèi)建函數(shù) super():
1 2 class Derived(Base): 3 def meth (self): 4 super(Derived, self).meth()
如果你使用的是經(jīng)典風(fēng)格的類:對于一個定義為 class Derived(Base): ... 的類,你可以調(diào)用Base(或Base某個基類)的方法 meth(),例如 Base.meth(self, arguments...).這里,Base.meth 是個未綁定的方法,你可以使用 self 參數(shù)。
你可以為基類定義一個別名,在你的類定義之前將真正的基類賦給它,并在你的整個類里使用別名。那么所有需要改變的就是賦給別名的值。另外,當(dāng)你想動態(tài)決定使用哪個基類的情況(例如,根據(jù)資源的有效性),這個技巧也很方便。例如:
1 2 BaseAlias = <real base class> 3 class Derived(BaseAlias): 4 def meth(self): 5 BaseAlias.meth(self) 6 ...
創(chuàng)建靜態(tài)數(shù)據(jù)(C++ 或 Java中的說法)很簡單;但不直接支持靜態(tài)方法(同樣是 C++ 或 Java中的說法)的創(chuàng)建。
對于靜態(tài)數(shù)據(jù),簡單地定義一個類屬性。當(dāng)給這個屬性賦予新值時,需要明確地使用類名稱。
1 2 class C: 3 count = 0 # number of times C.__init__ called 4 5 def __init__(self): 6 C.count = C.count + 1 7 8 def getcount(self): 9 return C.count # or return self.count
對于isinstance(c, C),c.count 同樣引用 C.count, 除非被c本身重載,或是被基類搜索路徑中從c.__class__到C上的某個類重載。
注意:在C的method中,類似 self.count = 42 的操作創(chuàng)建一個新的不相關(guān)實(shí)例,它在self本身的dict中被命名為 "count"。 重新邦定一個類靜態(tài)數(shù)據(jù)必須指定類,無論是在method里面還是外面:
1 2 C.count = 314
當(dāng)你使用新類型的類時,便有可能創(chuàng)建靜態(tài)方法:
1 2 class C: 3 def static(arg1, arg2, arg3): 4 # No 'self' parameter! 5 ... 6 static = staticmethod(static)
但是,創(chuàng)建靜態(tài)方法的另一個更直接的方式是使用一個簡單的模塊級別的函數(shù):
1 2 def getcount(): 3 return C.count
如果每個模塊可以定義一個類(或緊密相關(guān)的類結(jié)構(gòu)),就可提供相應(yīng)的封裝。
這個答案對于所有的method都適用,但是問題一般都先出現(xiàn)在構(gòu)造函數(shù)上。
在C++中你編寫
1 2 class C { 3 C() { cout << "No arguments\n"; } 4 C(int i) { cout << "Argument is " << i << "\n"; } 5 }
在python中,你只能編寫一個構(gòu)造函數(shù),通過使用默認(rèn)參數(shù)來處理所有的情況。例如:
1 2 class C: 3 def __init__(self, i=None): 4 if i is None: 5 print "No arguments" 6 else: 7 print "Argument is", i
這與C++并不相同,但在實(shí)際應(yīng)用中已相當(dāng)接近。
你也可以使用變長參數(shù)列表,例如
1 2 def __init__(self, *args): 3 ....
同樣的方法適用于所有的method定義。
有兩個前置下劃線的變量提供了一個簡單卻有效的定義類私有變量的方法。任何spam (至少兩個前置下劃線,最多一個后置下劃線)格式的標(biāo)志符都會被替換為_classnamespam, 其中classname 是目前的類名稱,并去掉了所有的前置下劃線。
這并不能保證私有性:一個外部的用戶可以直接連接到"_classnamespam" 屬性,且所有的私有變量在對象的 __dict__中都是可見的。很多Python程序員從來不為私有變量名稱煩惱。 有幾個可能的原因。 del 語句并不一定要調(diào)用 __del__ -- 它只是簡單地減少對象的引用計數(shù),如果已為零便調(diào)用 __del__ 。 如果你的數(shù)據(jù)結(jié)構(gòu)包含循環(huán)鏈接(例如,在一個樹種,每個child都有一個parent引用而每個parent都有一系列的child),那么引用計數(shù)永遠(yuǎn)不會返回至零。一旦python運(yùn)行一個算法來檢測這種循環(huán),但可能在你的數(shù)據(jù)結(jié)構(gòu)的最后一個引用結(jié)束后過一段時間才會運(yùn)行垃圾收集,所以你的 __del__ 方法會在一個隨機(jī)的時間被調(diào)用。當(dāng)你想reproduce錯誤時,這是很不方便的。更糟的是,對象的 __del__ 方法以任意順序執(zhí)行。你可以運(yùn)行 gc.collect() 來強(qiáng)制進(jìn)行收集,但是也可能會有對象永遠(yuǎn)不會被收集的情況。 盡管有循環(huán)收集,為對象明確地定義一個 close() 用來在完成任務(wù)后被調(diào)用,依然是一個好主意。那么close() 方法會刪除引用subobject的屬性。不要直接調(diào)用 __del__ -- __del__ 應(yīng)該調(diào)用 close() 而 close() 應(yīng)該確定對于相同的對象它可以不止一次地被調(diào)用。 另一個防止循環(huán)引用的方法就是使用 "weakref" 模塊,它允許你指向?qū)ο蠖粫黾右糜嫈?shù)。距離來說,對于樹數(shù)據(jù)結(jié)構(gòu),應(yīng)該對它們的parent和sibling(如果需要!)使用weak引用。 如果一個對象曾作為一個函數(shù)的local變量,而這個函數(shù)在一個except語句中捕獲一個表達(dá)式,那么情況有所變化,對這個對象的引用在那個函數(shù)的stack frame中且被stack trace包含,即,這個對象引用仍然存在。一般的,調(diào)用 sys.exc_clear()會清除最后一次記錄的異常,以解決這個問題。 最后,如果你的 __del__ 方法拋出一個異常,一個警告信息被輸出到 sys.stderr。 Python并不跟蹤某個類(或內(nèi)建數(shù)據(jù)類型)的所有實(shí)例。你可以通過在類的構(gòu)造函數(shù)中維護(hù)一個列表來跟蹤所有的實(shí)例。 當(dāng)一個模塊被首次import(或者是源代碼文件比目前已編譯好的文件更新)時,一個包含了編譯好的代碼的 .pyc 文件在這個 .py 文件所在的目錄中被創(chuàng)建。 .pyc文件創(chuàng)建失敗的一個可能原因是目錄的許可權(quán)限。舉例來說,你正在測試一個web服務(wù)器,編程時為某用戶,但運(yùn)行時又為另一用戶,就會發(fā)生這種情況。如果你import一個模塊,且python有這個能力(權(quán)限,剩余空間等)將編譯好的模塊寫回目錄,那么一個.pyc文件就會被自動創(chuàng)建。 在腳本的頂層運(yùn)行python時,則不會認(rèn)為引入了模塊,.pyc文件也不會被創(chuàng)建。例如,如果你有一個頂層的模塊 abc.py,而它import了另一個模塊 xyz.py, 當(dāng)你運(yùn)行abc時, xyz.pyc會在xyz被import時被創(chuàng)建,但是abc.pyc不會被創(chuàng)建,因?yàn)樗鼪]有被import。 如果你需要創(chuàng)建 abc.pyc -- 即,為一個并沒import的模塊創(chuàng)建 .pyc 文件 -- 你可以使用 py_compile 和 compileall 模塊。 py_compile 模塊可以手動地編譯任何模塊。一種方式是使用這個模塊的compile() 函數(shù)。 這會將.pyc 文件寫入abc.py 所在的目錄(或者可以通過可選參數(shù)cfile 改變它)。 你也可以使用compileall 模塊自動編譯一個或若干目錄中的所有文件。你可以在命令行里運(yùn)行 compileall.py 并指定要編譯的python文件的目錄。 通過global變量__name__ 某個模塊可以得知它的名稱。如果它的值為 '__main__',這個程序正作為一個腳本在運(yùn)行。 有很多模塊常通過import來使用,這些模塊也提供了一個命令行界面或自測試功能,這些代碼只在檢查了 __name__ 后運(yùn)行: 假設(shè)你有以下模塊: foo.py: bar.py: 解釋器按以下步驟執(zhí)行: 最后一步會失敗,因?yàn)閜ython還沒有解釋完 foo,而且foo的global symbol dictionary也是空的。 當(dāng)你import foo, 然后在global代碼中試圖連接 foo.foo_var 時也會發(fā)生同樣的事情。 至少有三個方法可解決這個問題。 Guidovan Rossum 建議避免所有的from <module> import...的用法,并將所有的代碼都移到函數(shù)中。global變量和類的初始化應(yīng)該只使用常量或內(nèi)建函數(shù)。這意味著對所有import的模塊的引用都使用<module>.<name> 的方式。 Jim Roskind 建議在每個模塊中采用以下步驟: van Rossum 并不是很喜歡這種方法,因?yàn)閕mport出現(xiàn)在一個奇怪的地方,但這樣確實(shí)可以工作。 Matthias Urlichs 建議i重寫你的代碼,使你不必在開頭就遞歸地import。 這些方法互相之間并不排斥。 用: 對于更現(xiàn)實(shí)的情況,你可能需要這樣做: 處于效率和連續(xù)性的原因,python只在模塊第一次被import時讀取模塊文件。如果不這樣,在一個包含很多模塊的程序中,若每個模塊都import另一個相同的模塊,會導(dǎo)致這個模塊被多次讀取。若要強(qiáng)行重讀某個模塊,這樣做: 警告:這種方法并不是100%有效。特別地,模塊包含以下語句 仍會使用舊版本的對象。如果模塊包含類定義,已存在的類實(shí)例也不會更新成新的定義。這會導(dǎo)致以下荒謬的結(jié)果: 如果你print這個類對象的話,就會搞清楚這個問題的實(shí)質(zhì)了: Email:liqust at gmail dot com ©2005 2pole1.6.11. 我的類定義了`__del__` 但在刪除對象時并沒有被調(diào)用.
1.6.12. 我如何得到一個給定類的所有實(shí)例 的列表?
1.7. 模塊
1.7.1. 如何創(chuàng)建一個 .pyc 文件?
1 2 >>> import py_compile 3 >>> py_compile.compile('abc.py')
1 2 python compileall.py .
1.7.2. 如何查到目前這個模塊的名稱?
1 2 def main(): 3 print 'Running test...' 4 ... 5 6 if __name__ '__main__': 7 main()
1.7.3. 如何讓模塊互相import?
1 2 from bar import bar_var 3 foo_var=1
1 2 from foo import foo_var 3 bar_var=2
1.7.4. `__import__`('x.y.z') 返回 <module 'x'>; 如何得到 z?
1 2 __import__('x.y.z').y.z
1 2 m = __import__(s) 3 for i in s.split(".")[1:]: 4 m = getattr(m, i)
1.7.5. 當(dāng)我對import的模塊修改并重新import后卻沒有出現(xiàn)應(yīng)有的改變,為什么?
1 2 import modname 3 reload(modname)
1 2 from modname import some_objects
1 2 >>> import cls 3 >>> c = cls.C() # Create an instance of C 4 >>> reload(cls) 5 <module 'cls' from 'cls.pyc'> 6 >>> isinstance(c, cls.C) # isinstance is false?!? 7 False
1 2 >>> c.__class__ 3 <class cls.C at 0x7352a0> 4 >>> cls.C 5 <class cls.C at 0x4198d0>