標籤:

python編程 FAQ

::-- limodou [2009-11-25 01:21:13]

目錄

  1. 編程 FAQ
    1. 一般問題
    2. 核心語言
    3. 數字和字元串
    4. 序列 (Tuples/Lists)
    5. 字典
    6. 對象
    7. 模塊

[翻譯]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

1. 編程 FAQ

  • Date: $Date: 2005-06-05 19:37:51 -0500 (Sun, 05 Jun 2005) $ Version: $Revision: 8226 $ Web site:

    http://www.python.org/

  • 1.1. 一般問題1.1.1. 是否有源碼級的調試器, 具有breakpoint, single-stepping等功能?

    是的。

    pdb模塊是一個簡單卻強大的命令行模式的python調試器。它是標準python庫的一部分, 在庫參考手冊中有關於它的文檔。作為一個例 子,你也可以使用pdb的代碼編寫你自己的調試器。

    作為標準python發行包的一部分(通常為Tools/scripts/idle),IDLE包含了一個圖形界面的調試器。在http://www.python.org/idle/doc/idle2.html#Debugger有IDLE調試器的文檔。

    另一個Python IDE,PythonWin包含了一個基於pdb的GUI調試器。 Pythonwin調試器對breakpoints作顏色標記,它還有一些很酷的特性,比如調試非python程序。可以參考http://www.python.org/windows/pythonwin/。最 新版本的PythonWin已作為ActivePython 發行包的一部分(見 http://www.activestate.com/Products/ActivePython/index.html)。

    Boa Constructor 是一個使用wxPython的IDE和GUI builder。它提供了可視化的框架創建和操作,一個對象探查器,多種代碼視圖比如對象瀏覽器,繼承架構,doc string創建的html文檔,一個高級調試器,繼承幫助和Zope支持。

    Eric3 是基於PyQt和Scintilla editing組件的一個IDE。

    Pydb是python標準調試器pdb的一個版本, 與DDD(Data Display Debugger, 一個流行的調試器圖形界面)一起工作。Pydb 可以在http://packages.debian.org/unstable/devel/pydb.html找到,DDD可以在 http://www.gnu.org/software/ddd找到.

    還有很多包含圖形界面的商業版本Python IDE。包括:

  • Wing IDE (http://wingide.com)

  • Komodo IDE (http://www.activestate.com/Products/Komodo)

  • 1.1.2. 是否有工具可以幫助找到bug或者做靜態分析?

    是的.

    PyChecker是一個靜態分析器,它可以找出python代碼中的bug並對代碼的複雜性和風格作出警告。可以在 http://pychecker.sf.net找到它.

    另一個工具Pylint 檢查一個模塊是否滿足編碼規範,它還支持插件擴展。除了PyChecker能提供的bug檢查外,Pylint 還提供額外的特性比如檢查代碼行長度,變數名是否符合代碼規範,聲明的介面是否都已實現等。http://www.logilab.org/projects/pylint/documentation 提供了關於Pylint特性的一個完整列表。

    1.1.3. 如何由python腳本創建一個單獨的二進位文件?

    如果你只是希望用戶運行一個單獨的程序而不需要預先下載一個python的發行版,則並不需要將Python代碼編譯成C代碼。有很多工具可以找出程序依賴的模塊並將這些模塊 與程序綁定在一起以產生一個單獨的執行文件。

    其中一種工具就是freeze tool, 它作為Tools/freeze被包含在python的代碼樹中。它將python位元組碼轉換成C數組,和一個可將你所有模塊嵌入到新程序中的編譯器,這個編譯器跟python模塊鏈接在一起。

    它根據import語句遞歸地掃描源代碼,並查找在標準python路徑中的模塊和源代碼目錄中的模塊(內建模塊)。用python寫的模塊的位元組碼隨後被轉換成C代碼(可以通過使用marshal模塊轉換成代碼對象的數組構 造器),併產生一個可自定義的配置文件,只包含程序使用了的模塊。最後將生成的C代碼編譯並鏈接至餘下的的python解釋器,產生一個與你的script執行效果完全一樣的單獨文件。

    顯然,freeze需要一個C編譯器。但也有一些工具並不需要。首先便是Gordon McMillan"s installer,它在

  • http://www.mcmillan-inc.com/install1.html

  • 它工作在Windows, Linux和至少是部分Unix變種上。

    另一個便是Thomas Heller的 py2exe (只適用於Windows平台),它在

  • http://starship.python.net/crew/theller/py2exe

  • 第三個是Christian Tismer的 SQFREEZE,它將位元組碼附在一個特殊的python解釋器後面,解釋器負責找到這段代碼。Python 2.4可能會引入類似的機制。

    其它工具包括Fredrik Lundh的 Squeeze 和 Anthony Tuininga的 cx_Freeze.

    1.1.4. 是否有關於python程序的代碼標準或風格嚮導?

    是的。標準庫模塊要求的代碼風格被列在PEP 8.

    1.1.5. 程序執行速度太慢,如何改善?

    一般來說這是個複雜的問題。有很多技巧可以提升python的速度,比如可以用C重寫部分代碼。

    在某些情況下將python轉換成C或x86彙編語言是可能的,這意味著您不需要修改代碼就可獲得速度提升。

    Pyrex 可以將稍許改動過的python碼轉換成C擴展,並可以在很多平台上使用。

    Psyco 是一個即時編譯器,可將python碼轉換成x86彙編語言。如果你可以使用它, Psyco 可使關鍵函數有明顯的性能提升。

    剩下的問題就是討論各種可稍許提升python代碼速度的技巧。在profile指出某個函數是一個經常執行的熱點後,除非確實需要,否則不要應用任何優化措施,優化經常會使代碼變得不清晰,您不應該承受這樣做所帶來的負擔(延長的開發時間,更多可能的bug),除非優化結果確實值得你這樣做。

    Skip Montanaro有一個專門關於提升python代碼速度的網頁,位於 http://manatee.mojam.com/~skip/python/fastpython.html。

    Guido van Rossum 寫了關於提升python代碼速度的內容,在http://www.python.org/doc/essays/list2str.html。

    還有件需要注意的事,那就是函數特別是方法的調用代價相當大;如果你設計了一個有很多小型函數的純面向對象的介面,而這些函數所做的不過是對實例變數獲取或賦值,又或是調用另一個方法,那麼你應該考慮使用更直接的方式比如直接存取實例變數。也可參照profile模塊(在Library Reference manual中描述),它 能找出程序哪些部分耗費多數時間(如果你有耐性的話--profile本身會使程序數量級地變慢)。

    記住很多從其它語言中學到的標準優化方法也可用於python編程。比如,在執行輸出時通過使用更大塊的寫入來減少系統調用會加快程序速度。因此CGI腳本一次性的寫入所有輸出就會比寫入很多次小塊輸出快得多。

    同樣的,在適當的情況下使用python的核心特性。比如,通過使用高度優化的C實現,slicing允許程序在解釋器主循環的一個滴答中,切割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.

    則更短且快得多。

    注意,內建函數如map(), zip(), 和friends在執行一個單獨循環的任務時,可被作為一個方便的加速器。比如將兩個list配成一對:

    切換行號顯示

    1 2 >>> zip([1,2,3], [4,5,6]) 3 [(1, 4), (2, 5), (3, 6)]

    或在執行一系列正弦值時:

    切換行號顯示

    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快得多,因為後者會計算很多次子表達式,而join()則在一次過程中完成所有的複製。對於字元串操作,對字元串對象使用replace()方法。僅當在沒有固定字元串模式時才使用正則表達式。考慮使用字元串格式化操作string % tuple和string % dictionary。

    使用內建方法list.sort()來排序,參考sorting mini-HOWTO中關於較高級的使用例子。除非在極特殊的情況下,list.sort()比其它任何 方式都要好。

    另一個技巧就是"將循環放入函數或方法中" 。例如,假設你有個運行的很慢的程序,而且你使用profiler確定函數ff()佔用了很多時間。如果你注意到ff():

    def ff(x):

  • ..do something with x computing result... return result
  • 常常是在循環中被調用,如:

    切換行號顯示

    1 2 list = map(ff, oldlist)

    或:

    切換行號顯示

    1 2 for x in sequence: 3 value = ff(x) 4 ...do something with value...

    那麼你可以通過重寫ff()來消除函數的調用開銷:

    切換行號顯示

    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...

    單獨對ff(x)的調用被翻譯成ffseq([x])[0],幾乎沒有額外開銷。當然這個技術並不總是合適的,還是其它的方法。

    你可以通過將函數或方法的定位結果精確地存儲至一個本地變數來獲得一些性能提升。一個循環如:

    切換行號顯示

    1 2 for key in token: 3 dict[key] = dict.get(key, 0) + 1

    每次循環都要定位dict.get。如果這個方法一直不變,可這樣實現以獲取小小的性能提升:

    切換行號顯示

    1 2 dict_get = dict.get # look up the method once 3 for key in token: 4 dict[key] = dict_get(key, 0) + 1

    默認參數可在編譯期被一次賦值,而不是在運行期。這隻適用於函數或對象在程序執行期間不被改變的情況,比如替換

    切換行號顯示

    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)

    因為這個技巧對常量變數使用了默認參數,因而需要保證傳遞給用戶API時不會產生混亂。

    1.2. 核心語言1.2.1. 如何在一個函數中設置一個全局變數?

    你是否做過類似的事?

    切換行號顯示

    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

    任何函數內賦值的變數都是這個函數的local變數。除非它專門聲明為global。作為函數體最後一個語句,x被賦值,因此編譯器認為x為local變數。而語句print x 試圖 print一個未初始化的local變數,因而會觸發NameError 異常。

    解決辦法是在函數的開頭插入一個明確的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。

    1.2.2. python中local和global變數的規則是什麼?

    在Python中,某個變數在一個函數里只是被引用,則認為這個變數是global。如果函數體中變數在某個地方會被賦值,則認為這個變數是local。如果一個global變數在函數體中 被賦予新值,這個變數就會被認為是local,除非你明確地指明其為global。

    儘管有些驚訝,我們略微思考一下就會明白。一方面,對於被賦值的變數,用關鍵字 global是為了防止意想不到的邊界效應。另一方面,如果對所有的global引用都需要關鍵字global,則會不停地使用global關鍵字。需要在每次引用內建函數或一個import的模塊時都聲明global。global聲明是用來確定邊界效應的,而這 種混亂的用法會抵消這個作用。

    1.2.3. 如何在模塊間共享global變數?

    在一個單獨程序中,各模塊間共享信息的標準方法是創建一個特殊的模塊(常被命名為config和cfg)。僅需要在你程序中每個模塊里import這個config模塊。 因為每個模塊只有一個實例,對這個模塊的任何改變將會影響所有的地方。例如:

    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

    注意,由於同樣的原因,使用模塊也是實現Singleton設計模式的基礎。

    1.2.4. 什麼是import模塊的最好方式?

    通常情況下,不要使用from modulename import * 這種格式。這樣做會使引入者的namespace混亂。很多人甚至對於那些專門設計用於這種模式的模塊都不採用這種方式。被設計成這種模式的模塊包括Tkinter, 和threading.

    在一個文件的開頭引入模塊。這樣做使得你的你的代碼需要哪些模塊變得清晰,並且避免了模塊名稱是否存在的問題。 在每行只使用一次import使得添加和刪除模塊import更加容易,但每行多個import則減少屏幕空間的使用。

    應該按照以下順序import模塊:

    1. 標準庫模塊 -- 如 sys, os, getopt 等
    2. 第三方模塊(安裝在python的site-packages目錄下) -- 如 mx.DateTime, ZODB, PIL.Image, 等。

    3. 本地實現的模塊。

    不要使用相對的import。如果你在編寫package.sub.m1 模塊的代碼並想 import package.sub.m2, 不要只是import m2, 即使這樣是合法的。用 from package.sub import m2 代替.相對的imports會導致模塊被初始化兩次,併產生奇怪的bug。

    有時需要將import語句移到函數或類中來防止import循環。 Gordon McMillan 說:

  • 在 兩個模塊都使用 "import <module>" 格式時是沒問題的 。但若第二個模塊想要獲取第一個模塊以外的一個名稱("from module import name")且這個import語句位於最頂層時,則會產生錯誤 。因為這時第一個模塊的名稱並不處於有效狀態,因為第一個模塊正忙於import第二個模塊。

  • 在這種情況下,如果第二個模塊只是用在一個函數中,那麼可以簡單地把import移入到這個函數中。當這個import被調用時,第一個模塊已經完成了初始化,而第二個模塊 則可以完成它的import語句了。

    如果某些模塊是系統相關的,那麼將import移出頂層代碼也是必要的。在那種情況下,甚至不可能在文件的頂層import所有的模塊。在這種情況下,在對應的系統相關代碼中引入這些模塊則是個好的選擇。

    在解決諸如防止import循環或試圖減少模塊初始化時間等問題,且諸多模塊並不需要依賴程序是如何執行的情況下,這種方法尤其有用。如果模塊只是被用在某個函數中,你也可以將import移到這個函數中。注意首次import模塊會花費較多的時間,但多次地import則幾乎不會再花去額外的時間,而只是需要兩次的字典查詢操作。即使模塊名稱已經處在scope外,這個模塊也很有可能 仍處在sys.modules中。

    如果只是某個類的實例使用某個模塊,則應該在類的__init__方法里import模塊並把這個模塊賦給一個實例變數以使這個模塊在對象的整個生命周期內一直有效(通過這個實例變數)。注意要使import推遲到類的實例化,必須將import放入某個方法中。在類里所有方法之外的地方放置import語句,仍然會 使模塊初始化的時候執行import。

    1.2.5. 如何將某個函數的選項或鍵值參數傳遞到另一個函數?

    在函數的參數列表中使用 * 和 ** ;它將你的位置參數作為一個tuple,將鍵值參數作為一個字典。當調用另一個函數時你可以通過使用 * 和 **來傳遞這些參數:

    切換行號顯示

    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)

    1.2.6. 如何編寫一個帶有輸出參數的函數(傳引用調用)?

    記住在python中參數傳遞是動過賦值實現的。因為賦值僅是創建一個新的對對象的引用,所以在調用者和被調用者之間沒有任何的別名可以使用,因此從本質上說沒有傳引用調用。但你可以通過一系列的方法來實現這個效果。

    1. 對結果傳遞一個tuple: 切換行號顯示

      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

      這通常是最清晰的方法。

    2. 通過使用global變數。這不是線程安全的,所以不推薦。
    3. 傳遞一個可變對象: 切換行號顯示

      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

    4. 傳遞一個可變字典: 切換行號顯示

      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"]

    5. 或者是將它綁定在一個類的實例中: 切換行號顯示

      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

      但這樣會使程序變得複雜,並不是一個好方法。

    最好的方法還是返回一個包含多個結果的tuple。

    1.2.7. 如何使用python中更高 階的函數?

    有兩個選擇:你可以使用內嵌的方式或使用可調用對象。比如,假設你想定義 linear(a,b),

    它返回計算a*x+b 的函數f(x)。使用內嵌的方法:

    切換行號顯示

    1 2 def linear(a,b): 3 def result(x): 4 return a*x + b 5 return result

    或者使用可調用的類:

    切換行號顯示

    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)

    給出一個可調用對象,taxes(10e6) 0.3 * 10e6 + 2。

    用可調用對象的方法有個缺點,那就是這樣做會慢一些且代碼也會長一些。但是,注意到一系列的可調用對象可通過繼承共享信號。

    切換行號顯示

    1 2 class exponential(linear): 3 # __init__ inherited 4 def __call__(self, x): 5 return self.a * (x ** self.b)

    對象可以對若干方法封裝狀態信息:

    切換行號顯示

    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() 運性起來就像是一組共享相同計數變數的函數。

    1.2.8. 如何在python中複製一個對象?

    通常,使用copy.copy() 或 copy.deepcopy()。並不是所有的對象都可以被複制,但大多數是可以的。

    某些對象可以被簡單地多的方法複製。字典有個copy() 方法:

    切換行號顯示

    1 2 newdict = olddict.copy()

    序列可以通過slicing來複制:

    切換行號顯示

    1 2 new_l = l[:]

    1.2.9. 如何查看某個對象的方法和屬性?

    對於一個用戶定義的類的實例x,dir(x) 返回一個按字母排序的列表,其中包含了這個實例的屬性和方法,類的屬性。

    1.2.10. 如何在運行時查看某個對象的名稱?

    一般來說是不行的,因為實際上對象並沒有名稱。實質上,賦值經常將一個名稱綁定到一個值;對於def 和 class 語句也是一樣, 但在那種情況下這個變數是可調用的。考慮以下代碼:

    切換行號顯示

    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進行調用,這個新創建的實例仍然被作為是類A的實例。但是,因為兩個名稱都被綁定到同樣的值,因此說這個實例的名稱到底是A還是B是不可能的。

    一般來說,讓你的代碼知道特定對象的名稱並不是必要的。除非去特意地編寫一個自省程序,否則這往往意味著需要改變一下代碼。

    在comp.lang.python, Fredrik Lundh 曾經給出了一個極好的解答:

  • 就好像在你家走廊發現一隻貓,而你想知道它的名字:這隻貓(對象)不會告訴你它的名字,它實際上也不在乎 —— 所以唯一的方法就是問你的鄰居們(名稱空間namespace)……
  • ...如果你發現它有很多名字或根本就沒有名字的話也不要驚訝!
  • 1.2.11. 是否有類似C的 "?:" 三元操作符?

    沒有。在很多情況下你可以用"a and b or c"模擬 a?b:c with , 但這樣做有個缺陷:如果b是zero(或 empty, 或None -- 只要為false)則c被選擇。在很多情況下你可以查看代碼以保證這種情況不會發生(例如,因為b是個常數或是一種永遠不會為false的類型),但是通常來書它的確是個問題。

    TimPeters (本人希望是Steve Majewski) 有以下建議: (a and [b] or [c])[0]. 因為 [b]是一個永遠不會為false的列表,所以錯誤的情況不會發生;然後對整個表達式使用 [0]來得到想要的b或者c。很難看,但在你重寫代碼並且使用"if"很不方便的情況下,這種方式是有效的。

    最好的方式還是用 if...else 語句。另一種方法就是用一個函數來實現 "?:" 操作符:

    切換行號顯示

    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)

    在大多數情況下,你會直接傳遞b和c: q(a,b,c)。為防止在不合適的情況下計算 b 或者 c,用一個lambda函數封裝它們,例如:q(a,lambda: b, lambda: c)。

    為什麼python沒有if-then-else表達式。有幾個回答:很多語言在沒有這個的情況下也工作得很好;它會減少可讀代碼的數量;還沒有足夠多的python風格的語法;通過對標準庫的搜索,發現幾乎沒有這種情況:通過使用 if-then-else 表達式讓代碼的可讀性更好。

    在 2002年, PEP 308 提交了若干語法建議,整個社區對此進行了一次非決定性的投票。很多人喜歡某個語法而反對另外的語法;投票結果表明,很多人寧願沒有三元操作符,也不願意創建一種新的令人討厭的語法,。

    1.2.12. 能不能在python中編寫複雜的行程序?

    是的。這經常發生在將lambda嵌入到lambda的情況,根據 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

    小朋友不要在家裡嘗試這個!

    1.3. 數字和字元串1.3.1. 如何指定十六進位和八進位整數?

    要指定一個八進位數字,在八進位之前加個0。例如,將a設置成八進位的10,輸入:

    切換行號顯示

    1 2 >>> a = 010 3 >>> a 4 8

    十六進位也很簡單。在十六進位前加個0x。十六進位數可以大寫也可以小寫。比如,在python解釋器中:

    切換行號顯示

    1 2 >>> a = 0xa5 3 >>> a 4 165 5 >>> b = 0XB2 6 >>> b 7 178

    1.3.2. 為什麼 -22 / 10 返回 -3?

    這是因為 i%j 跟 j 為同樣類型。如果你想那樣,且又想:

    切換行號顯示

    1 2 i (i/j)*j + (i%j)

    那麼整數除法就必須返回一個浮點值。C也有這個要求,編譯器截斷i/j,並使i的類型和i%j一樣。

    在實際應用中, i%j 的j是負數的可能性很小。當j是正數時,多數情況(實際上是所有情況)下i%j >= 0是很有用的。如果現在是10點,那麼200小時以前是多少? -190 % 12 2 是正確的,而-190 % 12 -10 則是個bug。

    1.3.3. 如何將一個字元串轉換成數字?

    對於整數,使用內建的 int() 類型構造器, 例如 int("144") 144。類似的,float() 轉換成浮點數,例如 float("144") 144.0。

    默認的,這些數字被解釋成十進位,所以 int("0144") 144 而 int("0x144") 則拋出ValueError異常。 int(string, base) 提供了第二個參數來指定類型,所以int("0x144", 16) 324。

    如果base被指定為0,則會按python的規則來解釋:開頭為一個 "0" 表示八進位,而 "0x" 表示十六進位。

    如果你只是將字元串轉換成數字,不用使用內建函數 eval()。eval()會慢很多並 有安全風險:某人傳遞給python一個表達式,可能會有意想不到的邊界效應。例如,某人傳遞 __import__("os").system("rm -rf $HOME") 會清除掉你的home目錄。

    eval()

    也能將數字解釋成為python表達式,所以 eval("09") 會給出一個語法錯誤,因為python將開頭為"0"的數字認為是八進位(base 8)。

    1.3.4. 如何將數字轉換成字元串?

    比如,數字144轉換成字元串 "144", 使用內建函數 str()。 如果想用八進位或十六進位表示,使用內建函數hex() 或oct()。對於格式化,使用 [../../doc/lib/typesseq-strings.html % operator],比如,"%04d" % 144 為 "0144" , "%.3f" % (1/3.0) 為 "0.333"。更多細節查看庫參考手冊。

    1.3.5. 如何在字元串的特定位置進行修改?

    不能,因為字元串是不可改的。如果你確實需要一個具有這個能力的對象,將字元串轉換成列表或使用數組模塊:

    切換行號顯示

    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.3.6. 如何使用字元串來調用函數/方法?

    這裡有幾種方式:

  • 最好的一種就是使用字典將字元串映射到函數。這種方法的最大好處就是字元串不用匹配函數的名稱。這也是模擬case construct的主要方式:
  • 切換行號顯示

    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

  • 使用內建函數getattr():
  • 切換行號顯示

    1 2 import foo 3 getattr(foo, "bar")()

    注意,getattr()可工作於任何對象,包括類,類實例,模塊等。 這種方法被用在標準庫中的若干地方,就像: 切換行號顯示

    1 2 class Foo: 3 def do_foo(self): 4 ... 5 6 def do_bar(self): 7 ... 8 9 f = getattr(foo_instance, "do_" + opname) 10 f()

  • 使用locals() 或 eval() 來獲得函數名稱:
  • 切換行號顯示

    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()

    注意:使用eval() 慢而且危險。如果你對字元串的內容沒有絕對控制權,其他人可能傳遞一個字元串而導致某個任意的函數被執行。

  • 1.3.7. 是否有跟Perl中的chomp()類似的函數,用來去除字元串結尾處的新行符?

    從Python 2.2起,可以使用S.rstrip("
    ") 來去除字元串S結尾處的任何行符,而不會去除結尾處的其它空白符。如果字元串S並不只是一行,並在末尾有若干個空行,所有空行的行符都會被去除:

    切換行號顯示

    1 2 >>> lines = ("line 1
    " 3 ... "
    " 4 ... "
    ") 5 >>> lines.rstrip("

    ") 6 "line 1 "

    當程序每次只能讀取一行數據時,這樣使用S.rstrip() 是很合適的。

    對於老版本的Python, 分別有兩個替代方法:

  • 如果想去除所有的結尾空白符,使用字元串對象的 rstrip() 方法。這樣會去掉所有的結尾空白符,而不只是一個新行符。
  • 另外,如果在字元串S中只有一行,使用 S.splitlines()[0]。
  • 1.3.8. 是否有實現 scanf() 或 sscanf() 功能的函數?

    沒有。

    對於簡單的輸入分析,最簡單的方法就是用string對象的 split() 將輸入行分割成若干用空格分割的單詞,然後用 int() 或float()將數字字元串轉換成數字。split() 支持可選參數 "sep" 用來處理分隔符不是空格的情況。

    對於更複雜的輸入分析,正則表達式比C的sscanf() 更強大和更合適。

    1.3.9. "UnicodeError: ASCII [decoding,encoding] error: ordinal not in range(128)" 是什麼意思?

    這個錯誤表明python只能處理 7-bit 的 ASCII 字元串。這裡有幾種方法可以解決這個問題。

    如果程序需要處理任意編碼的數據,程序的運行環境一般都會指定它傳給你的數據的編碼。你需要用那個編碼將輸入數據轉換成 Unicode數據。例如,一個處理 email 或web輸入的程序會在 Content-Type頭裡發現字元編碼信息。在稍後將數據轉換成Unicode時會使用到這個信息。假設通過 value 引用的字元串的編碼為 UTF-8:

    切換行號顯示

    1 2 value = unicode(value, "utf-8")

    會返回一個 Unicode 對象。如果數據沒有被正確地用 UTF-8 編碼,那麼這個調用會觸發一個 UnicodeError 異常。

    如果你只是想把非 ASCII 的數據轉換成 Unicode,你可以首先假定為 ASCII 編碼,如果失敗再產生 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 的文件中設定默認編碼。但並不推薦這樣 ,因為改變這個全局值可能會導致第三方的擴展模塊出錯。

    注意,在 Windows 上有一種編碼為 "mbcs",它是根據你目前的locale使用編碼。在很多情況下,尤其是跟 COM 一起工作的情況下,這是個合適的默認編碼。

    1.4. 序列 (Tuples/Lists)1.4.1. 如何在 tuples 和 lists 之間轉換?

    函數 tuple(seq) 將任何序列(實際上,任何可遍歷的對象)轉換成一個tuple,並有著同樣的元素和順序。

    例如,tuple([1, 2, 3]) 得到 (1, 2, 3),而 tuple("abc") 得到 ("a", "b", "c")。如果參數就是一個 tuple, 則不做任何複製而返回相同的對象,所以當你不確定一個對象是否是tuple時調用tuple()也沒有額外的開銷。

    函數 list(seq) 將任何序列或任何可遍歷的對象轉換成一個list,並有著同樣的元素和順序。比如 list((1, 2, 3)) 得到[1, 2, 3],而 list("abc") 得到 ["a", "b", "c"]。 如果參數就是一個列表,則執行一個複製操作,就像seq[:] 一樣。

    1.4.2. 什麼是負索引?

    Python序列的索引可正可負。若使用正索引,0是第一個索引,1是第二個索引,以此類推。若使用負索引,-1 表示最後一個索引,-2表示倒數第二個,以此類推。比如seq[-n] 與 seq[len(seq)-n] 相同。

    使用負索引帶來很大的方便。比如 S-1 是除最後一個字元外的所有字元串,這在移除字元串結尾處的換行符時非常有用。

    1.4.3. 如何反向遍歷一個序列?

    如果是一個列表, 最快的解決方法是

    切換行號顯示

    1 2 list.reverse() 3 try: 4 for x in list: 5 "do something with x" 6 finally: 7 list.reverse()

    這樣做有個缺點,就是當你在循環時,這個list被臨時反轉了。如果不喜歡這樣,也可做一個複製。這樣雖然看起來代價較大,但實際上比其它方法要快。

    切換行號顯示

    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>

    還有一個更優雅的方法,就是定義一個類,使它像一個序列一樣運行,並反向遍歷(根據 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>

    然而,由於方法調用的開銷,這是最慢的一種方法。

    當使用 Python 2.3時,你可以使用一種擴展的slice語法:

    切換行號顯示

    1 2 for x in sequence[::-1]: 3 <do something with x>

    1.4.4. 怎樣才能刪掉一個列表中的重複元素?

    Python Cookbook中有一個關於這個的較長的論述,提到了很多方法,參考:

  • http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560

  • 如果你不介意重新排列這個列表,那麼對它進行排序並從list的末尾開始掃描,將重複元素刪掉:

    切換行號顯示

    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.4.5. 如何在python中使用數組?

    使用列表:

    切換行號顯示

    1 2 ["this", 1, "is", "an", "array"]

    列表對等於C或Pascal中的數組;最大的不同是python的列表可以包含很多不同的數據類型。

    array 模塊也可以提供方法來創建緊湊表示的固定類型數組,但是它的索引會比 列錶慢。也要注意可定義類似數組且擁有各種特性的Numeric擴展和其它方式。

    要獲得Lisp風格鏈接的列表,你可以通過使用tuple來模擬cons cells。

    切換行號顯示

    1 2 lisp_list = ("like", ("this", ("example", None) ) )

    如果需要在運行時可更改,可以使用列表代替tuple。這裡類似lisp car 的是 lisp_list[0] ,而類似 cdr 的是 lisp_list[1]。 僅當你確定需要時使用它,因為這樣比使用python列錶慢很多。

    1.4.6. 如何使用多維列表?

    你很有可能用這種方式來產生一個多維數組:

    切換行號顯示

    1 2 A = [[None] * 2] * 3

    如果你pirnt它的話似乎是正確的:

    切換行號顯示

    1 2 >>> A 3 [[None, None], [None, None], [None, None]]

    但是當你賦一個值時,它會出現在好幾個地方:

  • 切換行號顯示

    1 2 >>> A[0][0] = 5 3 >>> A 4 [[5, None], [5, None], [5, None]]

  • 這是因為用 * 來複制時,只是創建了對這個對象的引用,而不是真正的創建了它。 *3 創建了一個包含三個引用的列表,這三個引用都指向同一個長度為2的列表。其中一個行的改變會顯示在所有行中,這當然不是你想要的。

    建議創建一個特定長度的list,然後用新的list填充每個元素:

    切換行號顯示

    1 2 A = [None]*3 3 for i in range(3): 4 A[i] = [None] * 2

    這樣創建了一個包含三個不同的長度為2的列表。你也可以使用list comprehension:

    切換行號顯示

    1 2 w,h = 2,3 3 A = [ [None]*w for i in range(h) ]

    或者,你可以使用一個擴展來提供矩陣數據類型;Numeric Python 是最有名的。

    1.4.7. 如何對一系列的對象應用方法?

    使用list comprehension:

    切換行號顯示

    1 2 result = [obj.method() for obj in List]

    更一般的,可以使用以下函數:

    切換行號顯示

    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)

    1.5. 字典1.5.1. 如何按特定的順序顯示一個字典?

    不能這樣。字典按不可預測的順序存儲數據,所以字典的顯示順序也是不可預測的。

    或許你正想保存一個可列印版本到一個文件,做某些更改後將其與其它顯示的字典比較,這個回答會使你感到沮喪。在這種情況下,使用pprint模塊來pretty-print字典,這樣元素會按鍵值排序。

    另一個複雜的多的方法就是繼承UserDict.UserDict 並創建類SortedDict ,以一個可預知的順序顯示出來。這裡有一個示例:

    切換行號顯示

    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__

    雖然這不是個完美的解決方案,但它可以在你遇到的很多情況下工作良好,最大的缺陷就是,如果字典中某個值也是字典,那麼將不會以任何特定順序顯示值。

    1.5.2. 我想做一個複雜的排序,可以在python中完成一個Schwartzian 變換嗎?

    可以,通過使用list comprehensions會非常簡單。

    根據Perl社區的Randal Schwartz的方法,創建一個矩陣,這個矩陣將列表的每個元素都映射到相應的「排序值」上,通過這個矩陣對列表進行排序。有一個字元串列表,用字元串的大寫字母值排序:

    切換行號顯示

    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位置的子域擴展的整數值進行排序:

    切換行號顯示

    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)

    但是因為這個方法對L的每個元素調用intfield()多次,所以要比Schwartzian變換慢。

    1.5.3. 如何根據一個list的值來對另一個list排序?

    將它們合併成一個包含若干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])

    如果你發現這樣做更清晰,你也許會使用這個替代方法。但是,對於長列表,它幾乎會花去大約兩倍的時間。為什麼?首先,append()操作需要重新分配內存,雖然它使用了一些技巧用來防止每次操作時都這樣做,它的消耗依然很大。第二,表達式 "result.append"需要一個額外的屬性定位,第三,所有的函數調用也會減慢速度。

    1.6. 對象1.6.1. 什麼是類?

    類是在執行類語句時創建的特殊對象。類對象被用來作為模板創建實例對象,它包含了針對某個數據類型的數據(屬性)和代碼(方法)。

    一個類可以繼承一個或多個被稱為基類的類。其繼承了基類的屬性和方法。這就允許通過繼承來對類進行重定義。假設有一個通用的Mailbox類提供基本的郵箱存取操作,那麼它的子類比如 MboxMailbox, MaildirMailbox, OutlookMailbox 可以處理各種特定的郵箱格式。

    1.6.2. 什麼是method(方法)?

    method就是某個在類x中的函數,一般調用格式為 x.name(arguments...)。 方法在類定義中被定義成函數:

    切換行號顯示

    1 2 class C: 3 def meth (self, arg): 4 return arg*2 + self.attribute

    1.6.3. 什麼是self?

    Self僅僅是method的第一個常規參數。一個method定義為 meth(self, a, b, c),

    對於定義這個method的類的某個實例x,調用時為 x.meth(a, b, c) ,而實際上是 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?]

    1.6.4. 如何確定某個對象是指定的類或子類的實例?

    使用內建函數 isinstance(obj, cls)。 你可以通過一個tuple來檢查某個對象是否是一系列類的實例,例如isinstance(obj, (class1, class2, ...)), 並檢查某個對象是否是python的內建類型,例如isinstance(obj, str) or isinstance(obj, (int, long, float, complex))。

    注意多數程序並不經常使用 isinstance() 來檢查用戶定義的類,如果你自己在編寫某個類,一個更好的面向對象風格的方法就是定義一個封裝特定功能的method,而不是檢查對象所屬的類然後根據這個來調用函數。例如,如果你有某個函數:

    切換行號顯示

    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()

    1.6.5. 什麼是delegation?

    Delegation是一個面向對象技術(也被稱為一種設計模式)。假設你有個類x並想改變它的某個方法method。 你可以創建一個新類,提供這個method的一個全新實現,然後將其它method都delegate到x中相應的method。

    Python程序員可以輕易地實現delegation。比如,下面這個類像一個文件一樣使用,但它將所有的數據都轉換成大寫:

    切換行號顯示

    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() 方法,在調用self.outfile.write()方法之前,將字元串參數 都轉換成大寫。所有其它的method都delegate到self.outfile 相應的method。這個delegation通過 __getattr__ 方法來完成;關於控制屬性存取的更多信息,參考 [../../doc/ref/attribute-access.html the language reference] 。

    注意到更多的情況下,delegation使人產生疑惑。 若需要修改屬性,還需要在類中定義__settattr__ 方法,並應小心操作。__setattr__ 的實現基本與以下一致:

    切換行號顯示

    1 2 class X: 3 ... 4 def __setattr__(self, name, value): 5 self.__dict__[name] = value 6 ...

    大多數__setattr__實現必須修改self.__dict__,用來存儲自身的本地狀態信息以防止無限遞歸。

    1.6.6. 如果繼承類里的某個方法覆蓋了基類中的定義,如何從繼承類中調用基類的這個方法?

    如果你使用的是新風格的類,使用內建函數 super():

    切換行號顯示

    1 2 class Derived(Base): 3 def meth (self): 4 super(Derived, self).meth()

    如果你使用的是經典風格的類:對於一個定義為 class Derived(Base): ... 的類,你可以調用Base(或Base某個基類)的方法 meth(),例如 Base.meth(self, arguments...).這裡,Base.meth 是個未綁定的方法,你可以使用 self 參數。

    1.6.7. 如何組織我的代碼以使改變基類更容易?

    你可以為基類定義一個別名,在你的類定義之前將真正的基類賦給它,並在你的整個類里使用別名。那麼所有需要改變的就是賦給別名的值。另外,當你想動態決定使用哪個基類的情況(例如,根據資源的有效性),這個技巧也很方便。例如:

    切換行號顯示

    1 2 BaseAlias = <real base class> 3 class Derived(BaseAlias): 4 def meth(self): 5 BaseAlias.meth(self) 6 ...

    1.6.8. 怎樣創建靜態類數據和靜態類方法?

    創建靜態數據(C++ 或 Java中的說法)很簡單;但不直接支持靜態方法(同樣是 C++ 或 Java中的說法)的創建。

    對於靜態數據,簡單地定義一個類屬性。當給這個屬性賦予新值時,需要明確地使用類名稱。

    切換行號顯示

    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 的操作創建一個新的不相關實例,它在self本身的dict中被命名為 "count"。 重新邦定一個類靜態數據必須指定類,無論是在method裡面還是外面:

    切換行號顯示

    1 2 C.count = 314

    當你使用新類型的類時,便有可能創建靜態方法:

    切換行號顯示

    1 2 class C: 3 def static(arg1, arg2, arg3): 4 # No "self" parameter! 5 ... 6 static = staticmethod(static)

    但是,創建靜態方法的另一個更直接的方式是使用一個簡單的模塊級別的函數:

    切換行號顯示

    1 2 def getcount(): 3 return C.count

    如果每個模塊可以定義一個類(或緊密相關的類結構),就可提供相應的封裝。

    1.6.9. 在python中如何重載構造函數?

    這個答案對於所有的method都適用,但是問題一般都先出現在構造函數上。

    在C++中你編寫

    切換行號顯示

    1 2 class C { 3 C() { cout << "No arguments
    "; } 4 C(int i) { cout << "Argument is " << i << "
    "; } 5 }

    在python中,你只能編寫一個構造函數,通過使用默認參數來處理所有的情況。例如:

    切換行號顯示

    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++並不相同,但在實際應用中已相當接近。

    你也可以使用變長參數列表,例如

    切換行號顯示

    1 2 def __init__(self, *args): 3 ....

    同樣的方法適用於所有的method定義。

    1.6.10. 我想使用 __spam 卻得到一個錯誤_SomeClassName__spam.

    有兩個前置下劃線的變數提供了一個簡單卻有效的定義類私有變數的方法。任何spam (至少兩個前置下劃線,最多一個後置下劃線)格式的標誌符都會被替換為_classnamespam, 其中classname 是目前的類名稱,並去掉了所有的前置下劃線。

    這並不能保證私有性:一個外部的用戶可以直接連接到"_classnamespam" 屬性,且所有的私有變數在對象的 __dict__中都是可見的。很多Python程序員從來不為私有變數名稱煩惱。

    1.6.11. 我的類定義了`__del__` 但在刪除對象時並沒有被調用.

    有幾個可能的原因。

    del 語句並不一定要調用 __del__ -- 它只是簡單地減少對象的引用計數,如果已為零便調用 __del__

    如果你的數據結構包含循環鏈接(例如,在一個樹種,每個child都有一個parent引用而每個parent都有一系列的child),那麼引用計數永遠不會返回至零。一旦python運行一個演算法來檢測這種循環,但可能在你的數據結構的最後一個引用結束後過一段時間才會運行垃圾收集,所以你的 __del__ 方法會在一個隨機的時間被調用。當你想reproduce錯誤時,這是很不方便的。更糟的是,對象的 __del__ 方法以任意順序執行。你可以運行 gc.collect() 來強制進行收集,但是也可能會有對象永遠不會被收集的情況。

    儘管有循環收集,為對象明確地定義一個 close() 用來在完成任務後被調用,依然是一個好主意。那麼close() 方法會刪除引用subobject的屬性。不要直接調用 __del__ -- __del__ 應該調用 close() 而 close() 應該確定對於相同的對象它可以不止一次地被調用。

    另一個防止循環引用的方法就是使用 "weakref" 模塊,它允許你指向對象而不會增加引用計數。距離來說,對於樹數據結構,應該對它們的parent和sibling(如果需要!)使用weak引用。

    如果一個對象曾作為一個函數的local變數,而這個函數在一個except語句中捕獲一個表達式,那麼情況有所變化,對這個對象的引用在那個函數的stack frame中且被stack trace包含,即,這個對象引用仍然存在。一般的,調用 sys.exc_clear()會清除最後一次記錄的異常,以解決這個問題。

    最後,如果你的 __del__ 方法拋出一個異常,一個警告信息被輸出到 sys.stderr。

    1.6.12. 我如何得到一個給定類的所有實例 的列表?

    Python並不跟蹤某個類(或內建數據類型)的所有實例。你可以通過在類的構造函數中維護一個列表來跟蹤所有的實例。

    1.7. 模塊1.7.1. 如何創建一個 .pyc 文件?

    當一個模塊被首次import(或者是源代碼文件比目前已編譯好的文件更新)時,一個包含了編譯好的代碼的 .pyc 文件在這個 .py 文件所在的目錄中被創建。

    .pyc文件創建失敗的一個可能原因是目錄的許可許可權。舉例來說,你正在測試一個web伺服器,編程時為某用戶,但運行時又為另一用戶,就會發生這種情況。如果你import一個模塊,且python有這個能力(許可權,剩餘空間等)將編譯好的模塊寫回目錄,那麼一個.pyc文件就會被自動創建。

    在腳本的頂層運行python時,則不會認為引入了模塊,.pyc文件也不會被創建。例如,如果你有一個頂層的模塊 abc.py,而它import了另一個模塊 xyz.py, 當你運行abc時, xyz.pyc會在xyz被import時被創建,但是abc.pyc不會被創建,因為它沒有被import。

    如果你需要創建 abc.pyc -- 即,為一個並沒import的模塊創建 .pyc 文件 -- 你可以使用 py_compile 和 compileall 模塊。

    py_compile 模塊可以手動地編譯任何模塊。一種方式是使用這個模塊的compile() 函數。

    切換行號顯示

    1 2 >>> import py_compile 3 >>> py_compile.compile("abc.py")

    這會將.pyc 文件寫入abc.py 所在的目錄(或者可以通過可選參數cfile 改變它)。

    你也可以使用compileall 模塊自動編譯一個或若干目錄中的所有文件。你可以在命令行里運行 compileall.py 並指定要編譯的python文件的目錄。

    切換行號顯示

    1 2 python compileall.py .

    1.7.2. 如何查到目前這個模塊的名稱?

    通過global變數__name__ 某個模塊可以得知它的名稱。如果它的值為 "__main__",這個程序正作為一個腳本在運行。 有很多模塊常通過import來使用,這些模塊也提供了一個命令行界面或自測試功能,這些代碼只在檢查了 __name__ 後運行:

    切換行號顯示

    1 2 def main(): 3 print "Running test..." 4 ... 5 6 if __name__ "__main__": 7 main()

    1.7.3. 如何讓模塊互相import?

    假設你有以下模塊:

    foo.py:

    切換行號顯示

    1 2 from bar import bar_var 3 foo_var=1

    bar.py:

    切換行號顯示

    1 2 from foo import foo_var 3 bar_var=2

    解釋器按以下步驟執行:

  • main imports foo
  • Empty globals for foo are created
  • foo is compiled and starts executing
  • foo imports bar
  • Empty globals for bar are created
  • bar is compiled and starts executing
  • bar imports foo (which is a no-op since there already is a module named foo)
  • bar.foo_var = foo.foo_var
  • 最後一步會失敗,因為python還沒有解釋完 foo,而且foo的global symbol dictionary也是空的。

    當你import foo, 然後在global代碼中試圖連接 foo.foo_var 時也會發生同樣的事情。

    至少有三個方法可解決這個問題。

    Guidovan Rossum 建議避免所有的from <module> import...的用法,並將所有的代碼都移到函數中。global變數和類的初始化應該只使用常量或內建函數。這意味著對所有import的模塊的引用都使用<module>.<name> 的方式。

    Jim Roskind 建議在每個模塊中採用以下步驟:

  • exports (globals, functions, and classes that don"t need imported base classes)
  • import statements
  • active code (including globals that are initialized from imported values).
  • van Rossum 並不是很喜歡這種方法,因為import出現在一個奇怪的地方,但這樣確實可以工作。

    Matthias Urlichs 建議i重寫你的代碼,使你不必在開頭就遞歸地import。

    這些方法互相之間並不排斥。

    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. 當我對import的模塊修改並重新import後卻沒有出現應有的改變,為什麼?

    處於效率和連續性的原因,python只在模塊第一次被import時讀取模塊文件。如果不這樣,在一個包含很多模塊的程序中,若每個模塊都import另一個相同的模塊,會導致這個模塊被多次讀取。若要強行重讀某個模塊,這樣做:

    切換行號顯示

    1 2 import modname 3 reload(modname)

    警告:這種方法並不是100%有效。特別地,模塊包含以下語句

    切換行號顯示

    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

    如果你print這個類對象的話,就會搞清楚這個問題的實質了:

  • 切換行號顯示

    1 2 >>> c.__class__ 3 <class cls.C at 0x7352a0> 4 >>> cls.C 5 <class cls.C at 0x4198d0>


  • Email:liqust at gmail dot com ?2005 2pole


    推薦閱讀:

    The Nature of Code(譯) — 前言(內含教學大綱)
    上線一星期後,來聊聊小程序的「預想之中」和「意料之外」?
    編程思維到底是什麼?
    如何向沒有計算機基礎的人解釋API介面是什麼?
    吐槽一篇講 Flutter/Dart 的文章

    TAG:編程 |