編程常見問題?
目錄
一般問題?
Python 有沒有提供斷點與單步調(diào)試等功能的,源碼層次的調(diào)試器??
有的。
以下介紹了一些 Python 的調(diào)試器,內(nèi)置函數(shù) breakpoint() 允許你使用其中的任何一種。
pdb 模塊是一個簡單但是夠用的控制臺模式 Python 調(diào)試器。 它是標準 Python 庫的一部分,并且 已收錄于庫參考手冊。 你也可以通過使用 pdb 代碼作為樣例來編寫你自己的調(diào)試器。
作為標準 Python 發(fā)行版附帶組件的 IDLE 交互式環(huán)境(通常位于 Tools/scripts/idle)中包含一個圖形化的調(diào)試器。
PythonWin 是一個包含有基于 pdb 的 GUI 調(diào)試器的 Python IDE。 Pythonwin 調(diào)試器會為斷點加上顏色,并具有許多很棒的特性,例如也可以非 Pythonwin 程序。 Pythonwin 是 Python for Windows Extensions 項目的一部分,也是 ActivePython 發(fā)行版的一部分(參見 https://www.activestate.com/activepython)。
Boa Constructor 是一個使用wxWidgets的IDE和GUI構(gòu)建器。它提供可視化框架創(chuàng)建和操作,對象檢查器,源對象瀏覽器上的許多視圖,繼承層次結(jié)構(gòu),doc字符串生成的html文檔,高級調(diào)試器,集成幫助和Zope支持。
Eric 是一個基于PyQt和Scintilla編輯組件構(gòu)建的IDE。
Pydb是標準Python調(diào)試器pdb的一個版本,經(jīng)過修改后可與DDD(數(shù)據(jù)顯示調(diào)試器)一起使用,DDD是一種流行的圖形化調(diào)試器前端。 Pydb可以在 http://bashdb.sourceforge.net/pydb/ 找到,DDD可以在 https://www.gnu.org/software/ddd 找到。
有許多商業(yè)Python IDE包括圖形調(diào)試器。他們包括:
Wing IDE (https://wingware.com/)
Komodo IDE (https://komodoide.com/)
PyCharm (https://www.jetbrains.com/pycharm/)
有沒有工具來幫助找尋漏洞或進行靜態(tài)分析??
有的。
PyChecker 是一個尋找Python代碼漏洞以及對代碼復(fù)雜性和風格給出警告的工具。你可以從這里獲得PyChecker: http://pychecker.sourceforge.net/ 。
Pylint 是另一個檢查模塊是否滿足編碼標準的工具,也可以編寫插件來添加自定義功能。除了PyChecker 執(zhí)行的錯誤檢查之外, Pylint 還提供了一些額外的功能,例如檢查行長度,變量名稱是否根據(jù)您的編碼標準格式良好,聲明的接口是否完全實現(xiàn)等等。 https://docs.pylint.org/ 提供了Pylint功能的完整列表。
我如何能夠通過一個 Python 腳本創(chuàng)建一個獨立運行的二進制文件??
如果你想要的只是一個獨立的程序,用戶可以下載和運行而不必先安裝Python發(fā)行版,你就不需要將Python編譯成C代碼。有許多工具可以確定程序所需的模塊集,并將這些模塊與Python二進制文件綁定在一起以生成單個可執(zhí)行文件。
一種是使用凍結(jié)工具,它包含在Python源代碼樹 Tools/freeze 中。它將Python字節(jié)代碼轉(zhuǎn)換為C數(shù)組;一個C編譯器,你可以將所有模塊嵌入到一個新程序中,然后將其與標準Python模塊鏈接。
它的工作原理是遞歸掃描源代碼以獲取import語句(兩種形式),并在標準Python路徑和源目錄(用于內(nèi)置模塊)中查找模塊。 然后,它將用Python編寫的模塊的字節(jié)碼轉(zhuǎn)換為C代碼(可以使用編組模塊轉(zhuǎn)換為代碼對象的數(shù)組初始化器),并創(chuàng)建一個定制的配置文件,該文件僅包含程序中實際使用的內(nèi)置模塊。 然后,它編譯生成的C代碼并將其與Python解釋器的其余部分鏈接,以形成一個獨立的二進制文件,其行為與你的腳本完全相同。
顯然, freeze 需要一個C編譯器。有幾個其他實用工具不需要。 一個是Thomas Heller的py2exe(僅限Windows)
另一個工具是 Anthony Tuininga 的 cx_Freeze。
是否有 Python 程序規(guī)范代碼標準或風格指南??
有的。 請參閱標準庫模塊所要求的代碼風格描述文檔 PEP 8 。
核心語言?
當變量有值時,為什么會出現(xiàn)UnboundLocalError??
通過在函數(shù)體中的某處添加賦值語句,導(dǎo)致以前正常工作的代碼被修改而得到 UnboundLocalError 會令人感到意外。
以下代碼:
>>> x = 10
>>> def bar():
... print(x)
>>> bar()
10
正常工作,但是以下代碼
>>> x = 10
>>> def foo():
... print(x)
... x += 1
會得到一個 UnboundLocalError :
>>> foo()
Traceback (most recent call last):
...
UnboundLocalError: local variable 'x' referenced before assignment
這是因為當你對作用域中的變量進行賦值時,該變量將成為該作用域的局部變量,并在外部作用域中隱藏任何類似命名的變量。由于foo中的最后一個語句為 x 分配了一個新值,編譯器會將其識別為局部變量。因此,當先前的 print(x) 嘗試打印未初始化的局部變量時會導(dǎo)致錯誤。
在上面的示例中,你可以通過將其聲明為全局來訪問外部作用域變量:
>>> x = 10
>>> def foobar():
... global x
... print(x)
... x += 1
>>> foobar()
10
這個顯式聲明是必需的,以便提醒你(與類和實例變量的表面類似情況不同),你實際上是在外部作用域中修改變量的值
>>> print(x)
11
你可以使用 nonlocal 關(guān)鍵字在嵌套作用域中執(zhí)行類似的操作:
>>> def foo():
... x = 10
... def bar():
... nonlocal x
... print(x)
... x += 1
... bar()
... print(x)
>>> foo()
10
11
Python中的局部變量和全局變量有哪些規(guī)則??
在Python中,僅在函數(shù)內(nèi)引用的變量是隱式全局變量。如果在函數(shù)體內(nèi)的任何位置為變量賦值,則除非明確聲明為全局,否則將其視為局部值。
雖然起初有點令人驚訝,但片刻考慮就可以解釋。一方面,要求 global 表示已分配的變量可以防止意外的副作用。另一方面,如果所有全局引用都需要 global ,那么你一直都在使用 global 。你必須將對內(nèi)置函數(shù)或?qū)肽K的組件的每個引用聲明為全局。這種雜亂會破壞 global 聲明用于識別副作用的有用性。
為什么在具有不同值的循環(huán)中定義的lambdas都返回相同的結(jié)果??
假設(shè)你使用for循環(huán)來定義幾個不同的 lambda (甚至是普通函數(shù)),例如::
>>> squares = []
>>> for x in range(5):
... squares.append(lambda: x**2)
這給你一個包含5個lambdas的列表,它們計算 x**2 。你可能會期望,當它們被調(diào)用時,它們將分別返回 0 、 1 、 4 、 9 和 16 。但是,當你真正嘗試時,你會看到它們都返回 16 。:
>>> squares[2]()
16
>>> squares[4]()
16
發(fā)生這種情況是因為 x 不是lambdas的內(nèi)部變量,而是在外部作用域中定義,并且在調(diào)用lambda時訪問它 - 而不是在定義它時。 在循環(huán)結(jié)束時, x 的值是 4 ,所以所有的函數(shù)現(xiàn)在返回 4**2 ,即 16 。你還可以通過更改 x 的值來驗證這一點,并查看lambdas的結(jié)果如何變化:
>>> x = 8
>>> squares[2]()
64
為了避免這種情況,你需要將值保存在lambdas的局部變量中,這樣它們就不依賴于全局``x`` 的值
>>> squares = []
>>> for x in range(5):
... squares.append(lambda n=x: n**2)
這里, n=x 在lambda本地創(chuàng)建一個新的變量 n ,并在定義lambda時計算,使它具有與 x 在循環(huán)中該點相同的值。這意味著 n 的值在第一個lambda中為 0 ,在第二個lambda中為 1 ,在第三個中為 2 ,依此類推。因此每個lambda現(xiàn)在將返回正確的結(jié)果:
>>> squares[2]()
4
>>> squares[4]()
16
請注意,這種行為并不是lambda所特有的,但也適用于常規(guī)函數(shù)。
如何跨模塊共享全局變量??
在單個程序中跨模塊共享信息的規(guī)范方法是創(chuàng)建一個特殊模塊(通常稱為config或cfg)。只需在應(yīng)用程序的所有模塊中導(dǎo)入配置模塊;然后該模塊可用作全局名稱。因為每個模塊只有一個實例,所以對模塊對象所做的任何更改都會在任何地方反映出來。 例如:
config.py:
x = 0 # Default value of the 'x' configuration setting
mod.py:
import config
config.x = 1
main.py:
import config
import mod
print(config.x)
請注意,出于同樣的原因,使用模塊也是實現(xiàn)Singleton設(shè)計模式的基礎(chǔ)。
導(dǎo)入模塊的“最佳實踐”是什么??
通常,不要使用 from modulename import * 。這樣做會使導(dǎo)入器的命名空間變得混亂,并且使得連接器更難以檢測未定義的名稱。
在文件的頂部導(dǎo)入模塊。這樣做可以清楚地了解代碼所需的其他模塊,并避免了模塊名稱是否在范圍內(nèi)的問題。每行導(dǎo)入一個模塊可以輕松添加和刪除導(dǎo)入的模塊,但每行導(dǎo)入多個模塊會占用更少的屏幕空間。
如果按以下順序?qū)肽K,這是一種很好的做法:
標準庫模塊 -- 例如:
sys,os,getopt,re第三方庫模塊(安裝在Python的site-packages目錄中的任何內(nèi)容) -- 例如mx.DateTime,ZODB,PIL.Image等
本地開發(fā)的模塊
有時需要將模塊導(dǎo)入語句移動到函數(shù)或類里面,以避免循環(huán)導(dǎo)入問題。Gordon McMillan 說:
當兩個模塊都使用 "import <module>" 的導(dǎo)入形式時,循環(huán)導(dǎo)入就可以了。但是當?shù)?2 個模塊想從第 1 個模塊中獲取一個名稱 ("from module import name") 并且導(dǎo)入位于頂層時,就會出錯。 這是因為第 1 個模塊中的名稱還不可用,因為第 1 個模塊正在忙著導(dǎo)入第 2 個模塊。
在這種情況下,如果第二個模塊僅用于一個函數(shù),則可以輕松地將模塊導(dǎo)入語句移動到該函數(shù)中。調(diào)用導(dǎo)入時,第一個模塊將完成初始化,第二個模塊可以進行導(dǎo)入。
如果某些模塊是特定于平臺的,則可能還需要將模塊導(dǎo)入語句移出頂級代碼。在這種情況下,甚至可能無法導(dǎo)入文件頂部的所有模塊。在這種情況下,在相應(yīng)的特定于平臺的代碼中導(dǎo)入正確的模塊是一個很好的選擇。
只有當需要解決諸如避免循環(huán)導(dǎo)入或試圖減少模塊初始化時間的問題時,才可以將導(dǎo)入移動到本地范圍,例如在函數(shù)定義中。如果根據(jù)程序的執(zhí)行方式,許多導(dǎo)入是不必要的,這種技術(shù)尤其有用。如果僅在某個函數(shù)中使用模塊,您還可能希望將導(dǎo)入移到該函數(shù)中。請注意,第一次加載模塊可能會因為模塊的一次初始化而代價高昂,但多次加載模塊實際上是免費的,只需進行幾次字典查找。即使模塊名稱超出了作用域,模塊也可能在 sys.modules 中可用。
為什么對象之間會共享默認值??
這種類型的缺陷通常會惹惱新手程序員。考慮這個函數(shù)
def foo(mydict={}): # Danger: shared reference to one dict for all calls
... compute something ...
mydict[key] = value
return mydict
第一次調(diào)用此函數(shù)時,mydict 包含一項。第二次,mydict 包含兩項,因為當 foo() 開始執(zhí)行時, mydict 中已經(jīng)有一項了。
函數(shù)調(diào)用經(jīng)常被期望為默認值創(chuàng)建新的對象。 但實際情況并非如此。 默認值會在函數(shù)定義時一次性地創(chuàng)建。 如果對象發(fā)生改變,就如本示例中的字典那樣,則對函數(shù)的后續(xù)調(diào)用將會引用這個被改變的對象。
按照定義,不可變對象例如數(shù)字、字符串、元組和 None 因為不可變所以是安全的。 對可變對象例如字典、列表和類實例的改變則可能造成迷惑。
由于這一特性,在編程中應(yīng)遵循的一項好習慣是不使用可變對象作為默認值。 而應(yīng)使用 None 作為默認值和函數(shù)中的值,檢查值為 None 的形參并創(chuàng)建相應(yīng)的列表、字典或其他可變對象。 例如,不要這樣寫:
def foo(mydict={}):
...
而要這樣寫:
def foo(mydict=None):
if mydict is None:
mydict = {} # create a new dict for local namespace
這一特性有時會很有用處。 當你有一個需要進行大量耗時計算的函數(shù)時,一個常見技巧是將每次調(diào)用函數(shù)的參數(shù)和結(jié)果值緩存起來,并在同樣的值被再次請求時返回緩存的值。 這稱為“記憶”,具體實現(xiàn)方式可以是這樣的:
# Callers can only provide two parameters and optionally pass _cache by keyword
def expensive(arg1, arg2, *, _cache={}):
if (arg1, arg2) in _cache:
return _cache[(arg1, arg2)]
# Calculate the value
result = ... expensive computation ...
_cache[(arg1, arg2)] = result # Store result in the cache
return result
你也可以使用包含一個字典的全局變量而不使用參數(shù)默認值;這完全取決于個人偏好。
如何將可選參數(shù)或關(guān)鍵字參數(shù)從一個函數(shù)傳遞到另一個函數(shù)??
使用函數(shù)參數(shù)列表中的 * 和 ** 說明符收集參數(shù);這會將位置參數(shù)作為元組,將關(guān)鍵字參數(shù)作為字典。然后,您可以使用 * 和 ** 調(diào)用另一個函數(shù)時傳遞這些參數(shù):
def f(x, *args, **kwargs):
...
kwargs['width'] = '14.3c'
...
g(x, *args, **kwargs)
形參和實參之間有什么區(qū)別??
形參 是指出現(xiàn)在函數(shù)定義中的名稱,而 實參 則是在調(diào)用函數(shù)時實際傳入的值。 形參定義了一個函數(shù)能接受何種類型的實參。 例如,對于以下函數(shù)定義:
def func(foo, bar=None, **kwargs):
pass
foo, bar 和 kwargs 是 func 的形參。 但是,在調(diào)用 func 時,例如:
func(42, bar=314, extra=somevar)
實際的值 42, 314 和 somevar 則是實參。
為什么更改列表 'y' 也會更改列表 'x'??
如果你編寫的代碼就像下面一樣:
>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]
你可能想知道為什么追加一個元素也改變了x。
產(chǎn)生這種結(jié)果有兩個因素:
變量只是指向具體對象的名稱。 執(zhí)行
y = x并不會為列表創(chuàng)建一個副本 —— 它只是創(chuàng)建了一個新變量y指向x所指向的同一對象。 這意味著只存在一個對象(列表),x和y都是對它的引用。列表屬于 mutable 對象,這意味著你可以改變它的內(nèi)容。
在調(diào)用 append() 之后,這個可變對象的內(nèi)容由 [] 變?yōu)?[10]。 由于兩個變量都指向同一對象,因此使用任何一個名稱所訪問到的都是修改后的值 [10]。
如果我們改為將不可變對象賦值給 x:
>>> x = 5 # ints are immutable
>>> y = x
>>> x = x + 1 # 5 can't be mutated, we are creating a new object here
>>> x
6
>>> y
5
我們可以看到在此情況下 x 和 y 就不再相等了。 這是因為整數(shù)是 immutable 對象,當我們執(zhí)行 x = x + 1 時我們并不是改變了 5 這個對象的值;而是創(chuàng)建了一個新的對象 (整數(shù) 6) 并將其賦值給 x (也就是改變了 x 所指向的對象)。 在賦值之后我們就有了兩個對象 (整數(shù) 6 和 5) 以及分別指向它們的兩個變量 (x 現(xiàn)在指向 6 而 y 仍然指向 5)。
某些操作 (例如 y.append(10) 和 y.sort()) 是改變原對象,而看上去相似的另一些操作 (例如 y = y + [10] 和 sorted(y)) 則是創(chuàng)建新對象。 通常在 Python 中 (以及在標準庫的所有代碼中) 會改變原對象的方法將返回 None 以幫助避免混淆這兩種不同類型的操作。 因此如果你錯誤地使用了 y.sort() 并期望它將返回一個經(jīng)過排序的 y 的副本,你得到的結(jié)果將會是 None,這將導(dǎo)致你的程序產(chǎn)生一個容易診斷的錯誤。
但是,還存在一類操作,不同的類型執(zhí)行相同的操作會有不同的行為:那就是增強賦值運算符。 例如,+= 會原地改變列表,但不會改變元組或整數(shù) (a_list += [1, 2, 3] 與 a_list.extend([1, 2, 3]) 一樣都會改變 a_list,而 some_tuple += (1, 2, 3) 和 some_int += 1 則會創(chuàng)建新的對象)。
換而言之:
如何編寫帶輸出參數(shù)的函數(shù)(通過引用調(diào)用)??
請記住在 Python 中參數(shù)是通過賦值來傳遞的。 由于賦值只是創(chuàng)建了對象的引用,因此在調(diào)用者和被調(diào)用者的參數(shù)名稱之間沒有別名,所以本身是沒有按引用調(diào)用的。 你可以通過多種方式實現(xiàn)所需的效果。
通過返回一個結(jié)果元組:
def func2(a, b): a = 'new-value' # a and b are local names b = b + 1 # assigned to new objects return a, b # return new values x, y = 'old-value', 99 x, y = func2(x, y) print(x, y) # output: new-value 100
這幾乎總是最清晰明了的解決方案。
通過使用全局變量。 這種方式不是線程安全的,而且也不受推薦。
通過傳遞一個可變 (即可原地修改的) 對象:
def func1(a): a[0] = 'new-value' # 'a' references a mutable list a[1] = a[1] + 1 # changes a shared object args = ['old-value', 99] func1(args) print(args[0], args[1]) # output: new-value 100
通過傳遞一個會被改變的字典:
def func3(args): args['a'] = 'new-value' # args is a mutable dictionary args['b'] = args['b'] + 1 # change it in-place args = {'a': 'old-value', 'b': 99} func3(args) print(args['a'], args['b'])
或者在一個類實例中捆綁值:
class callByRef: def __init__(self, **args): for (key, value) in args.items(): setattr(self, key, value) def func4(args): args.a = 'new-value' # args is a mutable callByRef args.b = args.b + 1 # change object in-place args = callByRef(a='old-value', b=99) func4(args) print(args.a, args.b)
幾乎沒有任何適當理由將問題如此復(fù)雜化。
你的最佳選擇是返回一個包含多個結(jié)果的元組。
如何在Python中創(chuàng)建高階函數(shù)??
你有兩種選擇:使用嵌套作用域,或者使用可調(diào)用對象。 例如,假設(shè)你想要定義 linear(a,b) 使其返回一個函數(shù) f(x) 來設(shè)計 a*x+b 的值。 可以使用以下嵌套作用域:
def linear(a, b):
def result(x):
return a * x + b
return result
或使用一個可調(diào)用對象:
class linear:
def __init__(self, a, b):
self.a, self.b = a, b
def __call__(self, x):
return self.a * x + self.b
在兩種情況下,:
taxes = linear(0.3, 2)
都會給出一個可調(diào)用對象,使得 taxes(10e6) == 0.3 * 10e6 + 2.
可調(diào)用對象方式的缺點是速度略慢且生成的代碼略長。 但是,請注意一組可調(diào)用對象能夠通過繼承來共享簽名:
class exponential(linear):
# __init__ inherited
def __call__(self, x):
return self.a * (x ** self.b)
對象可以封裝多個方法的狀態(tài):
class counter:
value = 0
def set(self, x):
self.value = x
def up(self):
self.value = self.value + 1
def down(self):
self.value = self.value - 1
count = counter()
inc, dec, reset = count.up, count.down, count.set
這里 inc(), dec() 和 reset() 將表現(xiàn)為共享同一計數(shù)變量的多個函數(shù)。
如何在Python中復(fù)制對象??
一般來說,通常情況下請嘗試 copy.copy() 或 copy.deepcopy()。 不是所有對象都可以復(fù)制,但多數(shù)都是可以的。
某些對象可以方便地復(fù)制。 例如字典具有 copy() 方法:
newdict = olddict.copy()
序列可以通過切片來復(fù)制:
new_l = l[:]
如何找到對象的方法或?qū)傩裕?/a>?
對于一個用戶自定義類的實例 x,dir(x) 將返回一個按字母順序排序的包含實例屬性和方法及其類所定義的屬性名稱的列表。
我的代碼如何才能發(fā)現(xiàn)對象的名稱??
通常來說是做不到的,因為對象并不真正具有名稱。 在本質(zhì)上,賦值總是會將一個名稱綁定到某個值;def 和 class 語句也是如此,但在這種情況下該值是一個可調(diào)用對象。 考慮以下代碼:
>>> class A:
... pass
...
>>> B = A
>>> a = B()
>>> b = a
>>> print(b)
<__main__.A object at 0x16D07CC>
>>> print(a)
<__main__.A object at 0x16D07CC>
不嚴謹?shù)刂v,該類有一個名稱:雖然它是綁定了兩個名稱并通過名稱 B 發(fā)起調(diào)用,所創(chuàng)建的實例仍然被視為類 A 的一個實例。 但是實例的名稱則無法確定地說是 a 或是 b,因為有兩個名稱被綁定到了同一個值。
一般來說你的代碼應(yīng)該沒有必要“知道”特定值的名稱。 除非你是在編寫特殊的內(nèi)省程序,出現(xiàn)這樣的問題通常表明如果改變方式可能會更有利。
在 comp.lang.python 中,F(xiàn)redrik Lundh 在回答這樣的問題時曾經(jīng)給出過一個絕佳的類比:
跟你找出在你家門廊見到的某只貓的名字所用的辦法一樣:貓(對象)自己無法告訴你它的名字,它根本就不在乎 —— 所以找出它叫什么名字的唯一辦法是問你的所有鄰居(命名空間)那是不是他們的貓(對象)……
……并且如果你發(fā)現(xiàn)它有很多名字或根本沒有名字也不必覺得驚訝!
逗號運算符的優(yōu)先級是什么??
逗號在 Python 中不是運算符。 考慮這個例子:
>>> "a" in "b", "a"
(False, 'a')
由于逗號不是運算符而是表達式之間的分隔符,以上代碼的含義就相當于:
("a" in "b"), "a"
而不是:
"a" in ("b", "a")
對于各種賦值運算符 (=, += 等) 來說同樣如此。 它們并不是真正的運算符而是賦值語句中的語法分隔符。
是否有與 C 的 "?:" 三目運算符等價的東西??
有的。 相應(yīng)語法如下:
[on_true] if [expression] else [on_false]
x, y = 50, 25
small = x if x < y else y
在 Python 2.5 引入此語法之前,常見的做法是使用邏輯運算符:
[expression] and [on_true] or [on_false]
然而這種做法并不保險,因為當 on_true 具有布爾假值時將會給出錯誤的結(jié)果。 所以,使用 ... if ... else ... 形式總是會更好。
是否可以用Python編寫混淆的單行程序??
可以。通常是在 lambda 中嵌套 lambda 來實現(xiàn)的。請參閱以下三個來自 Ulf Bartelt 的示例代碼:
from functools import reduce
# Primes < 1000
print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))
# First 10 Fibonacci numbers
print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:
f(x,f), range(10))))
# Mandelbrot set
print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+y,map(lambda y,
Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
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
>=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(
64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24))
# \___ ___/ \___ ___/ | | |__ lines on screen
# V V | |______ columns on screen
# | | |__________ maximum of "iterations"
# | |_________________ range on y axis
# |____________________________ range on x axis
請不要在家里嘗試,騷年!
函數(shù)參數(shù)列表中的斜杠(/)是什么意思??
函數(shù)參數(shù)列表中的斜杠表示在它之前的形參是僅限位置形參。 僅限位置形參沒有外部可用的名稱。 在調(diào)用接受僅限位置形參的函數(shù)時,參數(shù)只會基于它們的位置被映射到形參。 例如,pow() 是一個接受僅限位置形參的函數(shù)。 它的文檔是這樣的:
>>> help(pow)
Help on built-in function pow in module builtins:
pow(x, y, z=None, /)
Equivalent to x**y (with two arguments) or x**y % z (with three arguments)
Some types, such as ints, are able to use a more efficient algorithm when
invoked using the three argument form.
在形參列表末尾的斜杠意味著所有三個形參都是僅限位置形參。 因此,附帶關(guān)鍵字參數(shù)調(diào)用 pow() 將會導(dǎo)致報錯:
>>> pow(x=3, y=4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pow() takes no keyword arguments
請注意目前此特性只記錄于文檔而不是有效的 Python 語法,不過已經(jīng)有 PEP 570 提出了 Python 中使用僅位置形參的語法。
數(shù)字和字符串?
如何指定十六進制和八進制整數(shù)??
要指定一個八進制數(shù)碼,則在八進制值之前加一個零和一個小寫或大寫字母 "o" 作為前綴。 例如,要將變量 "a" 設(shè)為八進制的 "10" (十進制的 8),就輸入:
>>> a = 0o10
>>> a
8
十六進制數(shù)也同樣簡單。 只要在十六進制數(shù)之前加一個零和一個小寫或大寫字母 "x"。 十六進制數(shù)碼中的字母可以為大寫或小寫。 例如在 Python 解釋器中輸入:
>>> a = 0xa5
>>> a
165
>>> b = 0XB2
>>> b
178
為什么-22 // 10返回-3??
這主要是為了讓 i % j 的正負與 j 一致,如果你想要這樣的結(jié)果,并且又想要:
i == (i // j) * j + (i % j)
那么整除就必須向下取整。 C 同樣要求保持一致,并且編譯器在截短 i // j 的結(jié)果值時需要使 i % j 的正負與 i 一致。
對于 i % j 來說 j 為負值的應(yīng)用場景實際上是非常少的。 而 j 為正值的情況則非常多,并且實際上在所有情況下讓 i % j 的結(jié)果為 >= 0 會更有用處。 如果如果現(xiàn)在時間為 10 時,那么 200 小時前應(yīng)是幾時? -190 % 12 == 2 是有用處的;-190 % 12 == -10 則是會導(dǎo)致意外的漏洞。
如何將字符串轉(zhuǎn)換為數(shù)字??
對于整數(shù),可使用內(nèi)置的 int() 類型構(gòu)造器,例如 int('144') == 144。 類似地,可使用 float() 轉(zhuǎn)換為浮點數(shù),例如 float('144') == 144.0。
默認情況下,這些操作會將數(shù)字按十進制來解讀,因此 int('0144') == 144 而 int('0x144') 會引發(fā) ValueError。 int(string, base) 接受第二個可選參數(shù)指定轉(zhuǎn)換的基數(shù),例如 int('0x144', 16) == 324。 如果指定基數(shù)為 0,則按 Python 規(guī)則解讀數(shù)字:前綴 '0o' 表示八進制,而 '0x' 表示十六進制。
如果你只是想將字符串轉(zhuǎn)為數(shù)字,請不要使用內(nèi)置函數(shù) eval()。 eval() 的速度會慢很多并且有安全風險:別人可能會傳入具有你不想要的附帶效果的 Python 表達式。 例如,別人可以傳入 __import__('os').system("rm -rf $HOME") 這將刪除你的家目錄。
eval() 還具有將數(shù)字解讀為 Python 表達式的效果,這樣 eval('09') 將會導(dǎo)致語法錯誤,因為 Python 不允許十進制數(shù)的首位是 '0' ('0' 除外)。
如何將數(shù)字轉(zhuǎn)換為字符串??
例如要將數(shù)字 144 轉(zhuǎn)換為字符串 '144',可使用內(nèi)置類型構(gòu)造器 str()。 如果想要表示為十六進制或八進制數(shù),可使用內(nèi)置函數(shù) hex() 或 oct()。 想要更好地格式化,請參閱 格式化字符串字面值 和 格式字符串語法 等小節(jié),例如 "{:04d}".format(144) 生成 '0144' 而 "{:.3f}".format(1.0/3.0) 生成 '0.333'。
如何修改字符串??
無法修改,因為字符串是不可變對象。 在大多數(shù)情況下,你應(yīng)該使用你想要的各種部分來構(gòu)造一個新字符串。 但是,如果你想要一個可以原地修改 Unicode 數(shù)據(jù)的對象,可嘗試使用 io.StringIO 對象或 array 模塊:
>>> import io
>>> s = "Hello, world"
>>> sio = io.StringIO(s)
>>> sio.getvalue()
'Hello, world'
>>> sio.seek(7)
7
>>> sio.write("there!")
6
>>> sio.getvalue()
'Hello, there!'
>>> import array
>>> a = array.array('u', s)
>>> print(a)
array('u', 'Hello, world')
>>> a[0] = 'y'
>>> print(a)
array('u', 'yello, world')
>>> a.tounicode()
'yello, world'
如何使用字符串調(diào)用函數(shù)/方法??
有多種技巧可供選擇。
最好的做法是使用一個將字符串映射到函數(shù)的字典。 這一技巧的主要優(yōu)勢在于字符串不必與函數(shù)名稱一致。 這也是用于模擬其他語言中 case 結(jié)構(gòu)的主要技巧:
def a(): pass def b(): pass dispatch = {'go': a, 'stop': b} # Note lack of parens for funcs dispatch[get_input()]() # Note trailing parens to call function
使用內(nèi)置函數(shù)
getattr()import foo getattr(foo, 'bar')()
請注意
getattr()可用于任何對象,包括類、類實例、模塊等等。在標準庫中多次使用了這個技巧,例如:
class Foo: def do_foo(self): ... def do_bar(self): ... f = getattr(foo_instance, 'do_' + opname) f()
使用
locals()或eval()來解析出函數(shù)名:def myFunc(): print("hello") fname = "myFunc" f = locals()[fname] f() f = eval(fname) f()
注意:使用
eval()速度慢而且危險。 如果你不能絕對掌控字符串的內(nèi)容,別人將能傳入可被解析為任意函數(shù)直接執(zhí)行的字符串。
是否有與Perl 的chomp() 等效的方法,用于從字符串中刪除尾隨換行符??
可以使用 S.rstrip("\r\n") 從字符串 S 的末尾刪除所有的換行符,而不刪除其他尾隨空格。如果字符串 S 表示多行,且末尾有幾個空行,則將刪除所有空行的換行符:
>>> lines = ("line 1 \r\n"
... "\r\n"
... "\r\n")
>>> lines.rstrip("\n\r")
'line 1 '
由于通常只在一次讀取一行文本時才需要這樣做,所以使用 S.rstrip() 這種方式工作得很好。
是否有 scanf() 或 sscanf() 的對應(yīng)物??
沒有這樣的對應(yīng)物。
對于簡單的輸入解析,最方便的做法通常是使用字符串對象的 split() 方法將一行內(nèi)容拆解為以空格分隔的單詞,然后使用 int() 或 float() 將表示十進制數(shù)的字符串轉(zhuǎn)換為數(shù)值。 split() 支持可選的 "sep" 形參,適用于內(nèi)容行使用空格符以外的分隔符的情況。
以于更復(fù)雜的輸入解析,正則表達式會比 C 的 sscanf() 更強大,也更適合此類任務(wù)。
性能?
我的程序太慢了。該如何加快速度??
總的來說,這是個棘手的問題。首先,下面列出了深入了解前需要記住的事情:
不同的 Python 實現(xiàn)具有不同的性能特點。 本 FAQ 著重解答的是 CPython。
行為可能因操作系統(tǒng)而異,尤其是在談?wù)?I / O 或多線程時。
在嘗試優(yōu)化任何代碼 前 ,應(yīng)始終找到程序中的熱點(請參閱
profile模塊)。編寫基準腳本將允許您在搜索改進時快速迭代(請參閱
timeit模塊)。強烈建議在可能引入隱藏在復(fù)雜優(yōu)化中的回歸之前,要有良好的代碼覆蓋率(通過單元測試或任何其他技術(shù))。
話雖如此,加速Python代碼有很多技巧。以下是一些可以達到可接受的性能水平的一般原則:
使您的算法更快(或更改為更快的算法)可以產(chǎn)生比嘗試在代碼中使用微優(yōu)化技巧更大的好處。
使用正確的數(shù)據(jù)結(jié)構(gòu)。參考文檔 內(nèi)置類型 和
collections模塊。當標準庫提供用于執(zhí)行某些操作的原語時,可能(盡管不能保證)比您可能提出的任何替代方案更快。對于用C編寫的原語,例如內(nèi)置函數(shù)和一些擴展類型,這是真的。例如,請確保使用
list.sort()內(nèi)置方法或相關(guān)的sorted()函數(shù)進行排序(有關(guān)適度高級用法的示例,請參閱 排序指南 )。抽象傾向于創(chuàng)造間接性并迫使翻譯更多地工作。如果間接級別超過完成的有用工作量,則程序?qū)⒆兟?。你?yīng)該避免過度抽象,特別是在微小的功能或方法的形式下(這通常也會對可讀性產(chǎn)生不利影響)。
如果你已經(jīng)達到純 Python 允許的限制,那么有一些工具可以讓你走得更遠。 例如, Cython 可以將稍微修改的 Python 代碼版本編譯為 C 擴展,并且可以在許多不同的平臺上使用。 Cython 可以利用編譯(和可選的類型注釋)來使代碼明顯快于解釋運行時的速度。 如果您對 C 編程技能有信心,也可以自己 編寫 C 擴展模塊 。
參見
專門介紹 性能提示 的wiki頁面。
將多個字符串連接在一起的最有效方法是什么??
str 和 bytes 對象是不可變的,因此將多個字符串連接在一起效率很低,因為每個連接都會創(chuàng)建一個新對象。在一般情況下,總運行時間是總字符串長度的二次方。
要連接多個 str 對象,通常推薦的用法是將它們放入一個列表中并在結(jié)尾處調(diào)用 str.join() :
chunks = []
for s in my_strings:
chunks.append(s)
result = ''.join(chunks)
(另一個合理有效的慣用方法是 io.StringIO )
要連接多個 str 對象,建議使用本地連接( += 運算符)擴展 bytearray 對象:
result = bytearray()
for b in my_bytes_objects:
result += b
序列(元組/列表)?
如何在元組和列表之間進行轉(zhuǎn)換??
類型構(gòu)造器 tuple(seq) 可將任意序列(實際上是任意可迭代對象)轉(zhuǎn)換為具有相同排列順序的相同條目的元組。
例如,tuple([1, 2, 3]) 產(chǎn)生 (1, 2, 3) 而 tuple('abc') 產(chǎn)生 ('a', 'b', 'c')。 如果參數(shù)為一個元組,它不會創(chuàng)建副本而是返回同一對象,因此如果你不確定某個對象是否為元組時也可簡單地調(diào)用 tuple()。
類型構(gòu)造器 list(seq) 可將任意序列或可迭代對象轉(zhuǎn)換為具有相同排列順序的相同條目的列表。 例如,list((1, 2, 3)) 產(chǎn)生 [1, 2, 3] 而 list('abc') 產(chǎn)生 ['a', 'b', 'c']。 如果參數(shù)為一個列表,它會像 seq[:] 那樣創(chuàng)建一個副本。
什么是負數(shù)序號??
Python 序列使用正數(shù)或負數(shù)作為序號或稱索引號。 對于正數(shù)序號,第一個序號為 0 而 1 為第二個序號,依此類推。 對于負數(shù)序號,倒數(shù)第一個序號為 -1 而倒數(shù)第二個序號為 -2,依此類推。 可以認為 seq[-n] 就相當于 seq[len(seq)-n]。
使用負數(shù)序號有時會很方便。 例如 S[:-1] 就是原字符串去掉最后一個字符,這可以用來移除某個字符串末尾的換行符。
如何以相反的順序迭代序列??
使用 reversed() 內(nèi)置函數(shù),這是Python 2.4中的新功能:
for x in reversed(sequence):
... # do something with x ...
這不會修改您的原始序列,而是構(gòu)建一個反向順序的新副本以進行迭代。
在 Python 2.3 里,您可以使用擴展切片語法:
for x in sequence[::-1]:
... # do something with x ...
如何從列表中刪除重復(fù)項??
有關(guān)執(zhí)行此操作的許多方法的詳細討論,請參閱 Python Cookbook:
如果您不介意重新排序列表,請對其進行排序,然后從列表末尾進行掃描,刪除重復(fù)項:
if mylist:
mylist.sort()
last = mylist[-1]
for i in range(len(mylist)-2, -1, -1):
if last == mylist[i]:
del mylist[i]
else:
last = mylist[i]
如果列表的所有元素都可以用作設(shè)置鍵(即:它們都是 hashable ),這通常會更快:
mylist = list(set(mylist))
這會將列表轉(zhuǎn)換為集合,從而刪除重復(fù)項,然后返回到列表中。
如何在Python中創(chuàng)建數(shù)組??
使用列表:
["this", 1, "is", "an", "array"]
列表在時間復(fù)雜度方面相當于C或Pascal數(shù)組;主要區(qū)別在于,python列表可以包含許多不同類型的對象。
array 模塊還提供了創(chuàng)建具有緊湊表示的固定類型的數(shù)組的方法,但它的索引速度比列表慢。還要注意,數(shù)字擴展和其他擴展還定義了具有各種特性的類似數(shù)組的結(jié)構(gòu)。
要獲取Lisp樣式的列表,可以使用元組模擬cons單元:
lisp_list = ("like", ("this", ("example", None) ) )
如果需要可變性,可以使用列表而不是元組。這里模擬lisp car的是 lisp_list[0] ,模擬cdr的是 lisp_list[1] 。只有在你確定真的需要的時候才這樣做,因為它通常比使用Python列表慢得多。
如何創(chuàng)建多維列表??
你可能試圖制作一個像這樣的多維數(shù)組:
>>> A = [[None] * 2] * 3
如果你打印它,看起來是正確的:
>>> A
[[None, None], [None, None], [None, None]]
但是,當你給某一項賦值時,會同時在多個位置顯示變化:
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
其中的原因在于使用 * 對列表執(zhí)行重復(fù)操作并不是創(chuàng)建副本,它只是創(chuàng)建現(xiàn)有對象的引用。 *3 創(chuàng)建了對長度為二的同一列表的 3 個引用。 對某一行的改變會作用于所有行,通常這一定不是你所希望的。
建議的做法是先創(chuàng)建一個所需長度的列表,然后其中的元素再以一個新創(chuàng)建的列表來填充:
A = [None] * 3
for i in range(3):
A[i] = [None] * 2
這樣就生成了一個包含 3 個長度為二的不同列表的列表。 你也可以使用列表推導(dǎo)式:
w, h = 2, 3
A = [[None] * w for i in range(h)]
或者你還可以使用提供矩陣類型的擴展包;其中最著名的是 NumPy。
為什么 a_tuple[i] += ['item'] 會在執(zhí)行加法時引發(fā)異常??
這是由兩個事實共同導(dǎo)致的結(jié)果,一是增強賦值運算符屬于 賦值 運算符,二是在 Python 中存在可變和不可變兩種不同的對象。
此處的討論在任何對元組中指向可變對象的元素使用增強賦值運算符的情況都是普遍成立的,但在此我們只以 list 和 += 來舉例。
如果你寫成這樣:
>>> a_tuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
發(fā)生異常的原因是顯而易見的: 1 會與對象 a_tuple[0] 相加,而該對象為 (1),得到結(jié)果對象 2,但當我們試圖將運算結(jié)果 2 賦值給元組的 0 號元素時就將報錯,因為我們不能改變元組的元素所指向的對象。
在表層之處,以上增強賦值語句所做的大致是這樣:
>>> result = a_tuple[0] + 1
>>> a_tuple[0] = result
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
由于元組是不可變的,因此操作的賦值部分會引發(fā)錯誤。
當你這樣寫的時候:
>>> a_tuple = (['foo'], 'bar')
>>> a_tuple[0] += ['item']
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
發(fā)生異常會令人略感吃驚,還有一個更為令人吃驚的事實:雖然有報錯,但是添加操作卻生效了:
>>> a_tuple[0]
['foo', 'item']
要明白為何會這樣,你需要知道 (a) 如果一個對象實現(xiàn)了 __iadd__ 魔術(shù)方法,它會在執(zhí)行 += 增強賦值時被調(diào)用,并且其返回值將用于該賦值語句; (b) 對于列表來說,__iadd__ 等價于在列表上調(diào)用 extend 并返回該列表。 因此對于列表我們可以說 += 就是 list.extend 的“快捷方式”:
>>> a_list = []
>>> a_list += [1]
>>> a_list
[1]
這相當于:
>>> result = a_list.__iadd__([1])
>>> a_list = result
a_list 所引用的對象已被修改,而引用被修改對象的指針又重新被賦值給 a_list。 賦值的最終結(jié)果沒有變化,因為它是引用 a_list 之前所引用的同一對象的指針,但仍然發(fā)生了賦值操作。
因此,在我們的元組示例中,發(fā)生的事情等同于:
>>> result = a_tuple[0].__iadd__(['item'])
>>> a_tuple[0] = result
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
__iadd__ 成功執(zhí)行,因此列表得到了擴充,但是雖然 result 指向了 a_tuple[0] 已經(jīng)指向的同一對象,最后的賦值仍然導(dǎo)致了報錯,因為元組是不可變的。
我想做一個復(fù)雜的排序:你能用Python做一個Schwartzian變換嗎??
該技術(shù)歸功于Perl社區(qū)的 Randal Schwartz,它通過將每個元素映射到其 "排序值(sort value)" 的度量對列表中的元素進行排序。在Python中,使用 list.sort() 方法的 key 參數(shù):
Isorted = L[:]
Isorted.sort(key=lambda s: int(s[10:15]))
如何按其他列表中的值對一個列表進行排序??
將它們合并到元組的迭代器中,對結(jié)果列表進行排序,然后選擇所需的元素。
>>> list1 = ["what", "I'm", "sorting", "by"]
>>> list2 = ["something", "else", "to", "sort"]
>>> pairs = zip(list1, list2)
>>> pairs = sorted(pairs)
>>> pairs
[("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
>>> result = [x[1] for x in pairs]
>>> result
['else', 'sort', 'to', 'something']
最后一步的替代方案是:
>>> result = []
>>> for p in pairs: result.append(p[1])
如果你覺得這個更容易讀懂,那么你可能更喜歡使用這個而不是前面的列表推導(dǎo)。然而,對于長列表來說,它的速度幾乎是原來的兩倍。為什么?首先, append() 操作必須重新分配內(nèi)存,雖然它使用了一些技巧來避免每次都這樣做,但它仍然偶爾需要這樣做,而且代價相當高。第二,表達式 "result.append" 需要額外的屬性查找。第三,必須執(zhí)行所有這些函數(shù)調(diào)用會降低速度。
對象?
什么是類??
類 是通過執(zhí)行類語句創(chuàng)建的特定對象類型。類對象 被當作模板來創(chuàng)建實例對象,實例對象包含了特定于數(shù)據(jù)類型的數(shù)據(jù)(屬性)和代碼(方法)。
類可以基于一個或多個的其他類,稱之為基類(ES),它繼承基類的屬性和方法,這樣就可以通過繼承來連續(xù)地細化對象模型。例如:您可能有一個 Mailbox 類提供郵箱的基本訪問方法.,它的子類 MboxMailbox, MaildirMailbox, OutlookMailbox 用于處理各種特定郵箱格式。
什么是方法??
方法 實際上就是類定義中的函數(shù)。對于某個對象 x 上的方法,通常稱為 x.name(arguments...) 。
class C:
def meth(self, arg):
return arg * 2 + self.attribute
什么是 self ??
Self 只是 方法 的第一個參數(shù)的常規(guī)名稱。例如:對于某個類的某個實例 x ,其方法 meth(self, a, b, c) 實際上應(yīng)該被稱為 x.meth(a, b, c) ;對于被調(diào)用的方法會被稱為 meth(x, a, b, c) 。
如何檢查對象是否為給定類或其子類的一個實例??
可使用內(nèi)置函數(shù) isinstance(obj, cls)。 你可以提供一個元組而不是單個類來檢查某個對象是否為任意多個類當中某一個類的實例,例如 isinstance(obj, (class1, class2, ...)),也可以檢查某個對象是否為 Python 內(nèi)置類型當中某一個類型的對象,例如 isinstance(obj, str) 或 isinstance(obj, (int, float, complex))。
請注意大多數(shù)程序不會經(jīng)常對用戶自定義類使用 isinstance()。 如果是你自已開發(fā)的類,更正確的面向?qū)ο箫L格是在類中定義方法來封裝特定的行為,而不是檢查對象的類并根據(jù)它屬于什么類來做不同的事。 例如,如果你有一個執(zhí)行某些操作的函數(shù):
def search(obj):
if isinstance(obj, Mailbox):
... # code to search a mailbox
elif isinstance(obj, Document):
... # code to search a document
elif ...
更好的方法是在所有類上定義一個 search() 方法,然后調(diào)用它:
class Mailbox:
def search(self):
... # code to search a mailbox
class Document:
def search(self):
... # code to search a document
obj.search()
什么是委托??
委托是一種面向?qū)ο蟮募记桑ㄒ卜Q為設(shè)計模式)。 假設(shè)您有一個對象 x 并且想要改變其中一個方法的行為。 您可以創(chuàng)建一個新類,它提供您感興趣的方法的新實現(xiàn),并將所有其他方法委托給 x 的相應(yīng)方法。
Python程序員可以輕松實現(xiàn)委托。 例如,以下類實現(xiàn)了一個類,該類的行為類似于文件,但將所有寫入的數(shù)據(jù)轉(zhuǎn)換為大寫:
class UpperOut:
def __init__(self, outfile):
self._outfile = outfile
def write(self, s):
self._outfile.write(s.upper())
def __getattr__(self, name):
return getattr(self._outfile, name)
在這里 UpperOut 類重新定義了 write() 方法在調(diào)用下層的 self._outfile.write() 方法之前將參數(shù)字符串轉(zhuǎn)換為大寫形式。 所有其他方法都被委托給下層的 self._outfile 對象。 委托是通過 __getattr__ 方法來完成的;請參閱 語言參考 了解有關(guān)控制屬性訪問的更多信息。
請注意對于更一般的情況來說,委托可能包含更多細節(jié)問題。 當某些屬性既需要讀取又需要設(shè)置時,類還必須定義 __setattr__() 方法,并且這樣做必須小心謹慎。 __setattr__() 的基本實現(xiàn)大致相當于以下代碼:
class X:
...
def __setattr__(self, name, value):
self.__dict__[name] = value
...
大多數(shù) __setattr__() 實現(xiàn)必須修改 self.__dict__ 來為自身保存局部狀態(tài)而又不至于造成無限遞歸。
如何從覆蓋基類的派生類調(diào)用基類中定義的方法??
使用內(nèi)置的 super() 函數(shù):
class Derived(Base):
def meth(self):
super(Derived, self).meth()
對于 Python 3.0之前的版本,您可能正在使用經(jīng)典類:對于諸如 class Derived(Base): ... 之類的類定義,可以將在 Base (或 Base 中的一個的基類)中定義的方法 meth() 調(diào)用為 Base.meth(self, arguments...) 。這里, Base.meth 是一個未綁定的方法,因此您需要提供 self 參數(shù)。
如何組織代碼以便更改基類??
可以為基類定義別名,在類定義之前為其分配實際基類,并在整個類中使用別名。然后更改分配給別名的值,就能實現(xiàn)上述要求。順便提一下,如果你想動態(tài)決定(例如,取決于資源的可用性)要使用哪個基類,這個技巧也很方便。例如:
BaseAlias = <real base class>
class Derived(BaseAlias):
def meth(self):
BaseAlias.meth(self)
...
如何創(chuàng)建靜態(tài)類數(shù)據(jù)和靜態(tài)類方法??
Python支持靜態(tài)數(shù)據(jù)和靜態(tài)方法(在C ++或Java的意義上)。
對于靜態(tài)數(shù)據(jù),只需定義一個類屬性。要為屬性分配新值,就必須在賦值中顯式使用類名:
class C:
count = 0 # number of times C.__init__ called
def __init__(self):
C.count = C.count + 1
def getcount(self):
return C.count # or return self.count
對于任意 c 來說只要 isinstance(c, C) 為真,則 c.count 同樣也指向 C.count,除非被 c 自身,或者從 c.__class__ 回到 C 的基類搜索路徑上的某個類所重載。
注意:在 C 的某個方法內(nèi)部,像 self.count = 42 這樣的賦值將在 self 自身的字典中新建一個名為 "count" 的不相關(guān)實例。 想要重新綁定類靜態(tài)數(shù)據(jù)名稱就必須總是指明類名,無論是在方法內(nèi)部還是外部:
C.count = 314
靜態(tài)方法是可行的:
class C:
@staticmethod
def static(arg1, arg2, arg3):
# No 'self' parameter!
...
然而,獲得靜態(tài)方法效果的更直接的方法是通過一個簡單的模塊級函數(shù):
def getcount():
return C.count
如果您的代碼是結(jié)構(gòu)化的,以便為每個模塊定義一個類(或緊密相關(guān)的類層次結(jié)構(gòu)),那么這就提供了所需的封裝。
如何在Python中重載構(gòu)造函數(shù)(或方法)??
這個答案實際上適用于所有方法,但問題通常首先出現(xiàn)在構(gòu)造函數(shù)的上下文中。
在C ++中,你會這樣寫
class C {
C() { cout << "No arguments\n"; }
C(int i) { cout << "Argument is " << i << "\n"; }
}
在Python中,您必須編寫一個構(gòu)造函數(shù),使用默認參數(shù)捕獲所有情況。例如:
class C:
def __init__(self, i=None):
if i is None:
print("No arguments")
else:
print("Argument is", i)
這不完全等同,但在實踐中足夠接近。
你也可以嘗試一個可變長度的參數(shù)列表,例如:
def __init__(self, *args):
...
相同的方法適用于所有方法定義。
我嘗試使用 __spam ,但是得到一個關(guān)于 _SomeClassName__spam 的錯誤信息。?
以雙下劃線打頭的變量會被“更名”以提供一種定義類私有變量的簡單而有效的方式。 任何形式為 __spam 的標識符(至少前綴兩個下劃線,至多后綴一個下劃線)文本會被替換為 _classname__spam,其中 classname 為去除了全部前綴下劃線的當前類名稱。
這并不能保證私密性:外部用戶仍然可以訪問 "_classname__spam" 屬性,私有變量值也在對象的 __dict__ 中可見。 許多 Python 程序員從來都不使用這種私有變量名稱。
類定義了 __del__ 方法,但是刪除對象時沒有調(diào)用它。?
這有幾個可能的原因。
del 語句不一定調(diào)用 __del__() —— 它只是減少對象的引用計數(shù),如果(引用計數(shù))達到零,才會調(diào)用 __del__()。
如果數(shù)據(jù)結(jié)構(gòu)包含循環(huán)鏈接(例如,每個子級都有一個父級引用,每個父級都有一個子級列表的樹),則引用計數(shù)將永遠不會返回零。盡管Python 偶爾會運行一個算法來檢測這樣的循環(huán),但在數(shù)據(jù)結(jié)構(gòu)的引用計數(shù)清零后,垃圾收集器可能需要一段時間來運行,因此 __del__() 方法可能會在不方便和隨機的時間被調(diào)用。這對于重現(xiàn)一個問題,是非常不方便的。更糟糕的是,對象 __del__() 的方法執(zhí)行順序是任意的。雖然可以運行 gc.collect() 來強制回收,但在一些病態(tài)的情況下,對象永遠不會被回收。
盡管有循環(huán)收集器,但在對象上定義一個顯式的 close() 方法以便在用完之后調(diào)用它仍然是一個好主意。 這樣 close() 方法可以隨即刪除引用子對象的屬性。 不要直接調(diào)用 __del__() —— 應(yīng)該由 __del__() 調(diào)用 close(),并且 close() 能確??梢员煌粚ο蠖啻蔚卣{(diào)用。
另一種避免循環(huán)引用的方法是使用 weakref 模塊,該模塊允許您指向?qū)ο蠖辉黾悠湟糜嫈?shù)。例如,樹狀數(shù)據(jù)結(jié)構(gòu)應(yīng)該對其父級和同級引用使用弱引用(如果需要的話?。?/p>
最后,如果 __del__() 方法引發(fā)異常,會將警告消息打印到 sys.stderr 。
如何獲取給定類的所有實例的列表??
Python不跟蹤類(或內(nèi)置類型)的所有實例。您可以對類的構(gòu)造函數(shù)進行編程,以通過保留每個實例的弱引用列表來跟蹤所有實例。
為什么 id() 的結(jié)果看起來不是唯一的??
id() 返回一個整數(shù),該整數(shù)在對象的生命周期內(nèi)保證是唯一的。因為在CPython中,這是對象的內(nèi)存地址,所以經(jīng)常發(fā)生在從內(nèi)存中刪除對象之后,下一個新創(chuàng)建的對象被分配在內(nèi)存中的相同位置。這個例子說明了這一點:
>>> id(1000)
13901272
>>> id(2000)
13901272
這兩個id屬于之前創(chuàng)建的不同整數(shù)對象,并在執(zhí)行 id() 調(diào)用后立即刪除。要確保要檢查其id的對象仍處于活動狀態(tài),請創(chuàng)建對該對象的另一個引用:
>>> a = 1000; b = 2000
>>> id(a)
13901272
>>> id(b)
13891296
模塊?
如何創(chuàng)建 .pyc 文件??
當一個模塊首次被導(dǎo)入時(或自當前已編譯文件創(chuàng)建后源文件被修改時),將會在對應(yīng) .py 文件所在目錄的 __pycache__ 子目錄下創(chuàng)建一個包含已編譯代碼的 .pyc 文件。 該 .pyc 文件的文件名的開頭部分將與對應(yīng) .py 文件名相同,并以 .pyc 為后綴,中間部門則是基于創(chuàng)建它的特定 python 二進制代碼版本。 (詳情參見 PEP 3147。)
無法創(chuàng)建 .pyc 文件的可能原因是包含源文件的目錄存在權(quán)限問題,這意味著 __pycache__ 子目錄無法被創(chuàng)建。 舉例來說,如果你以某一用戶來開發(fā)程序但以另一用戶身份來運行程序時就可能發(fā)生問題,測試 Web 服務(wù)器就屬于這種情況。
除非設(shè)置了 PYTHONDONTWRITEBYTECODE 環(huán)境變量,否則當你導(dǎo)入模塊并且 Python 具有創(chuàng)建 __pycache__ 子目錄并將已編譯模塊寫入該子目錄的能力(權(quán)限、存儲空間等等)時就會自動創(chuàng)建 .pyc 文件。
在最高層級運行的 Python 腳本不被視為導(dǎo)入,因此不會創(chuàng)建 .pyc 文件。 例如,如果你有一個最高層級模塊文件 foo.py,它又導(dǎo)入了另一個模塊 xyz.py,當你運行 foo 模塊 (通過輸入終端命令 python foo.py),則將為 xyz 創(chuàng)建一個 .pyc,因為 xyz 是被導(dǎo)入的,但不會為 foo 創(chuàng)建 .pyc 文件,因為 foo.py 不是被導(dǎo)入的。
如果你需要為 foo 創(chuàng)建 .pyc 文件 —— 即為不是被導(dǎo)入的模塊創(chuàng)建 .pyc 文件 —— 你可以使用 py_compile 和 compileall 模塊。
py_compile 模塊能夠手動編譯任意模塊。 一種做法是交互式地使用該模塊中的 compile() 函數(shù):
>>> import py_compile
>>> py_compile.compile('foo.py')
這將會將 .pyc 文件寫入與 foo.py 相同位置下的 __pycache__ 子目錄(或者你也可以通過可選參數(shù) cfile 來重載該行為)。
你還可以使用 compileall 模塊自動編譯一個目錄或多個目錄下的所有文件。 具體做法可以是在命令行提示符中運行 compileall.py 并提供包含要編譯 Python 文件的目錄路徑:
python -m compileall .
如何找到當前模塊名稱??
模塊可以通過查看預(yù)定義的全局變量 __name__ 找到自己的模塊名稱。如果它的值為 '__main__' ,程序?qū)⒆鳛槟_本運行。通常,通過導(dǎo)入使用的許多模塊也提供命令行界面或自檢,并且只在檢查 __name__ 之后,才執(zhí)行之后的代碼:
def main():
print('Running test...')
...
if __name__ == '__main__':
main()
如何讓模塊相互導(dǎo)入??
假設(shè)您有以下模塊:
foo.py:
from bar import bar_var
foo_var = 1
bar.py:
from foo import foo_var
bar_var = 2
問題是解釋器將執(zhí)行以下步驟:
首先導(dǎo)入foo
創(chuàng)建用于foo的空全局變量
foo被編譯并開始執(zhí)行
foo 導(dǎo)入 bar
創(chuàng)建了用于bar 的空全局變量
bar被編譯并開始執(zhí)行
bar導(dǎo)入foo(這是一個空操作(no-op ),因為已經(jīng)有一個名為foo的模塊)
bar.foo_var = foo.foo_var
最后一步失敗了,因為Python還沒有解釋foo,而foo的全局符號字典仍然是空的。
當你使用 import foo ,然后嘗試在全局代碼中訪問 foo.foo_var 時,會發(fā)生同樣的事情。
這個問題有(至少)三種可能的解決方法。
Guido van Rossum 建議避免使用 from <module> import ... ,并將所有代碼放在函數(shù)中。全局變量和類變量的初始化只能使用常量或內(nèi)置函數(shù)。這意味著導(dǎo)入模塊中的所有內(nèi)容都被引用為 <module>.<name> 。
Jim Roskind建議在每個模塊中按以下順序執(zhí)行步驟:
導(dǎo)出(全局變量,函數(shù)和不需要導(dǎo)入基類的類)
導(dǎo)入聲明活動代碼(包括從導(dǎo)入值初始化的全局變量)。
van Rossum不喜歡這種方法,因為導(dǎo)入出現(xiàn)在一個陌生的地方,但這種方法確實有效。
Matthias Urlichs建議重構(gòu)代碼,以便首先不需要遞歸導(dǎo)入。
這些解決方案并不相互排斥。
__import__('x.y.z') 返回 <module 'x'>; 如何獲取z??
考慮使用 importlib 中的函數(shù) import_module() :
z = importlib.import_module('x.y.z')
當我編輯了導(dǎo)入過的模塊并重新導(dǎo)入它時,這些變化沒有顯示出來。為什么會這樣??
出于效率和一致性的原因,Python僅在第一次導(dǎo)入模塊時讀取模塊文件。如果不這么做,在一個由許多模塊組成的程序中,每個模塊都會導(dǎo)入相同的基本模塊,那么基本模塊將被解析和重新解析多次。要強制重新讀取已更改的模塊,請執(zhí)行以下操作:
import importlib
import modname
importlib.reload(modname)
警告:這種技術(shù)不是100%萬無一失。特別是包含如下語句的模塊
from modname import some_objects
將繼續(xù)使用舊版本的導(dǎo)入對象。如果模塊包含類定義,則不會更新現(xiàn)有的類實例以使用新的類定義。這可能導(dǎo)致以下矛盾行為:
>>> import importlib
>>> import cls
>>> c = cls.C() # Create an instance of C
>>> importlib.reload(cls)
<module 'cls' from 'cls.py'>
>>> isinstance(c, cls.C) # isinstance is false?!?
False
如果打印出類對象的“標識”,問題的本質(zhì)就會明確:
>>> hex(id(c.__class__))
'0x7352a0'
>>> hex(id(cls.C))
'0x4198d0'
