編程常見(jiàn)問(wèn)題?

目錄

一般問(wèn)題?

Python 有沒(méi)有提供斷點(diǎn)與單步調(diào)試等功能的,源碼層次的調(diào)試器??

有的。

以下介紹了一些 Python 的調(diào)試器,內(nèi)置函數(shù) breakpoint() 允許你使用其中的任何一種。

pdb 模塊是一個(gè)簡(jiǎn)單但是夠用的控制臺(tái)模式 Python 調(diào)試器。 它是標(biāo)準(zhǔn) Python 庫(kù)的一部分,并且 已收錄于庫(kù)參考手冊(cè)。 你也可以通過(guò)使用 pdb 代碼作為樣例來(lái)編寫(xiě)你自己的調(diào)試器。

作為標(biāo)準(zhǔn) Python 發(fā)行版附帶組件的 IDLE 交互式環(huán)境(通常位于 Tools/scripts/idle)中包含一個(gè)圖形化的調(diào)試器。

PythonWin 是一個(gè)包含有基于 pdb 的 GUI 調(diào)試器的 Python IDE。 Pythonwin 調(diào)試器會(huì)為斷點(diǎn)加上顏色,并具有許多很棒的特性,例如也可以非 Pythonwin 程序。 Pythonwin 是 Python for Windows Extensions 項(xiàng)目的一部分,也是 ActivePython 發(fā)行版的一部分(參見(jiàn) https://www.activestate.com/activepython)。

Boa Constructor 是一個(gè)使用wxWidgets的IDE和GUI構(gòu)建器。它提供可視化框架創(chuàng)建和操作,對(duì)象檢查器,源對(duì)象瀏覽器上的許多視圖,繼承層次結(jié)構(gòu),doc字符串生成的html文檔,高級(jí)調(diào)試器,集成幫助和Zope支持。

Eric 是一個(gè)基于PyQt和Scintilla編輯組件構(gòu)建的IDE。

Pydb是標(biāo)準(zhǔn)Python調(diào)試器pdb的一個(gè)版本,經(jīng)過(guò)修改后可與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)試器。他們包括:

有沒(méi)有工具來(lái)幫助找尋漏洞或進(jìn)行靜態(tài)分析??

有的。

PyChecker 是一個(gè)尋找Python代碼漏洞以及對(duì)代碼復(fù)雜性和風(fēng)格給出警告的工具。你可以從這里獲得PyChecker: http://pychecker.sourceforge.net/

Pylint 是另一個(gè)檢查模塊是否滿(mǎn)足編碼標(biāo)準(zhǔn)的工具,也可以編寫(xiě)插件來(lái)添加自定義功能。除了PyChecker 執(zhí)行的錯(cuò)誤檢查之外, Pylint 還提供了一些額外的功能,例如檢查行長(zhǎng)度,變量名稱(chēng)是否根據(jù)您的編碼標(biāo)準(zhǔn)格式良好,聲明的接口是否完全實(shí)現(xiàn)等等。 https://docs.pylint.org/ 提供了Pylint功能的完整列表。

靜態(tài)類(lèi)型檢查器,例如 MypyPyrePytype 可以檢查Python源代碼中的類(lèi)型提示。

我如何能夠通過(guò)一個(gè) Python 腳本創(chuàng)建一個(gè)獨(dú)立運(yùn)行的二進(jìn)制文件??

如果你想要的只是一個(gè)獨(dú)立的程序,用戶(hù)可以下載和運(yùn)行而不必先安裝Python發(fā)行版,你就不需要將Python編譯成C代碼。有許多工具可以確定程序所需的模塊集,并將這些模塊與Python二進(jìn)制文件綁定在一起以生成單個(gè)可執(zhí)行文件。

一種是使用凍結(jié)工具,它包含在Python源代碼樹(shù) Tools/freeze 中。它將Python字節(jié)代碼轉(zhuǎn)換為C數(shù)組;一個(gè)C編譯器,你可以將所有模塊嵌入到一個(gè)新程序中,然后將其與標(biāo)準(zhǔn)Python模塊鏈接。

它的工作原理是遞歸掃描源代碼以獲取import語(yǔ)句(兩種形式),并在標(biāo)準(zhǔn)Python路徑和源目錄(用于內(nèi)置模塊)中查找模塊。 然后,它將用Python編寫(xiě)的模塊的字節(jié)碼轉(zhuǎn)換為C代碼(可以使用編組模塊轉(zhuǎn)換為代碼對(duì)象的數(shù)組初始化器),并創(chuàng)建一個(gè)定制的配置文件,該文件僅包含程序中實(shí)際使用的內(nèi)置模塊。 然后,它編譯生成的C代碼并將其與Python解釋器的其余部分鏈接,以形成一個(gè)獨(dú)立的二進(jìn)制文件,其行為與你的腳本完全相同。

顯然, freeze 需要一個(gè)C編譯器。有幾個(gè)其他實(shí)用工具不需要。 一個(gè)是Thomas Heller的py2exe(僅限Windows)

另一個(gè)工具是 Anthony Tuininga 的 cx_Freeze

是否有 Python 程序規(guī)范代碼標(biāo)準(zhǔn)或風(fēng)格指南??

有的。 請(qǐng)參閱標(biāo)準(zhǔn)庫(kù)模塊所要求的代碼風(fēng)格描述文檔 PEP 8

核心語(yǔ)言?

當(dāng)變量有值時(shí),為什么會(huì)出現(xiàn)UnboundLocalError??

通過(guò)在函數(shù)體中的某處添加賦值語(yǔ)句,導(dǎo)致以前正常工作的代碼被修改而得到 UnboundLocalError 會(huì)令人感到意外。

以下代碼:

>>> x = 10
>>> def bar():
...     print(x)
>>> bar()
10

正常工作,但是以下代碼

>>> x = 10
>>> def foo():
...     print(x)
...     x += 1

會(huì)得到一個(gè) UnboundLocalError :

>>> foo()
Traceback (most recent call last):
  ...
UnboundLocalError: local variable 'x' referenced before assignment

這是因?yàn)楫?dāng)你對(duì)作用域中的變量進(jìn)行賦值時(shí),該變量將成為該作用域的局部變量,并在外部作用域中隱藏任何類(lèi)似命名的變量。由于foo中的最后一個(gè)語(yǔ)句為 x 分配了一個(gè)新值,編譯器會(huì)將其識(shí)別為局部變量。因此,當(dāng)先前的 print(x) 嘗試打印未初始化的局部變量時(shí)會(huì)導(dǎo)致錯(cuò)誤。

在上面的示例中,你可以通過(guò)將其聲明為全局來(lái)訪(fǎng)問(wèn)外部作用域變量:

>>> x = 10
>>> def foobar():
...     global x
...     print(x)
...     x += 1
>>> foobar()
10

這個(gè)顯式聲明是必需的,以便提醒你(與類(lèi)和實(shí)例變量的表面類(lèi)似情況不同),你實(shí)際上是在外部作用域中修改變量的值

>>> print(x)
11

你可以使用 nonlocal 關(guān)鍵字在嵌套作用域中執(zhí)行類(lèi)似的操作:

>>> 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)的任何位置為變量賦值,則除非明確聲明為全局,否則將其視為局部值。

雖然起初有點(diǎn)令人驚訝,但片刻考慮就可以解釋。一方面,要求 global 表示已分配的變量可以防止意外的副作用。另一方面,如果所有全局引用都需要 global ,那么你一直都在使用 global 。你必須將對(duì)內(nèi)置函數(shù)或?qū)肽K的組件的每個(gè)引用聲明為全局。這種雜亂會(huì)破壞 global 聲明用于識(shí)別副作用的有用性。

為什么在具有不同值的循環(huán)中定義的lambdas都返回相同的結(jié)果??

假設(shè)你使用for循環(huán)來(lái)定義幾個(gè)不同的 lambda (甚至是普通函數(shù)),例如::

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda: x**2)

這給你一個(gè)包含5個(gè)lambdas的列表,它們計(jì)算 x**2 。你可能會(huì)期望,當(dāng)它們被調(diào)用時(shí),它們將分別返回 014916 。但是,當(dāng)你真正嘗試時(shí),你會(huì)看到它們都返回 16 。:

>>> squares[2]()
16
>>> squares[4]()
16

發(fā)生這種情況是因?yàn)?x 不是lambdas的內(nèi)部變量,而是在外部作用域中定義,并且在調(diào)用lambda時(shí)訪(fǎng)問(wèn)它 - 而不是在定義它時(shí)。 在循環(huán)結(jié)束時(shí), x 的值是 4 ,所以所有的函數(shù)現(xiàn)在返回 4**2 ,即 16 。你還可以通過(guò)更改 x 的值來(lái)驗(yàn)證這一點(diǎn),并查看lambdas的結(jié)果如何變化:

>>> x = 8
>>> squares[2]()
64

為了避免這種情況,你需要將值保存在lambdas的局部變量中,這樣它們就不依賴(lài)于全局``x`` 的值

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda n=x: n**2)

這里, n=x 在lambda本地創(chuàng)建一個(gè)新的變量 n ,并在定義lambda時(shí)計(jì)算,使它具有與 x 在循環(huán)中該點(diǎn)相同的值。這意味著 n 的值在第一個(gè)lambda中為 0 ,在第二個(gè)lambda中為 1 ,在第三個(gè)中為 2 ,依此類(lèi)推。因此每個(gè)lambda現(xiàn)在將返回正確的結(jié)果:

>>> squares[2]()
4
>>> squares[4]()
16

請(qǐng)注意,這種行為并不是lambda所特有的,但也適用于常規(guī)函數(shù)。

如何跨模塊共享全局變量??

在單個(gè)程序中跨模塊共享信息的規(guī)范方法是創(chuàng)建一個(gè)特殊模塊(通常稱(chēng)為config或cfg)。只需在應(yīng)用程序的所有模塊中導(dǎo)入配置模塊;然后該模塊可用作全局名稱(chēng)。因?yàn)槊總€(gè)模塊只有一個(gè)實(shí)例,所以對(duì)模塊對(duì)象所做的任何更改都會(huì)在任何地方反映出來(lái)。 例如:

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)

請(qǐng)注意,出于同樣的原因,使用模塊也是實(shí)現(xiàn)Singleton設(shè)計(jì)模式的基礎(chǔ)。

導(dǎo)入模塊的“最佳實(shí)踐”是什么??

通常,不要使用 from modulename import * 。這樣做會(huì)使導(dǎo)入器的命名空間變得混亂,并且使得連接器更難以檢測(cè)未定義的名稱(chēng)。

在文件的頂部導(dǎo)入模塊。這樣做可以清楚地了解代碼所需的其他模塊,并避免了模塊名稱(chēng)是否在范圍內(nèi)的問(wèn)題。每行導(dǎo)入一個(gè)模塊可以輕松添加和刪除導(dǎo)入的模塊,但每行導(dǎo)入多個(gè)模塊會(huì)占用更少的屏幕空間。

如果按以下順序?qū)肽K,這是一種很好的做法:

  1. 標(biāo)準(zhǔn)庫(kù)模塊 -- 例如: sys, os, getopt, re

  2. 第三方庫(kù)模塊(安裝在Python的site-packages目錄中的任何內(nèi)容) -- 例如mx.DateTime,ZODB,PIL.Image等

  3. 本地開(kāi)發(fā)的模塊

有時(shí)需要將模塊導(dǎo)入語(yǔ)句移動(dòng)到函數(shù)或類(lèi)里面,以避免循環(huán)導(dǎo)入問(wèn)題。Gordon McMillan 說(shuō):

當(dāng)兩個(gè)模塊都使用 "import <module>" 的導(dǎo)入形式時(shí),循環(huán)導(dǎo)入就可以了。但是當(dāng)?shù)?2 個(gè)模塊想從第 1 個(gè)模塊中獲取一個(gè)名稱(chēng) ("from module import name") 并且導(dǎo)入位于頂層時(shí),就會(huì)出錯(cuò)。 這是因?yàn)榈?1 個(gè)模塊中的名稱(chēng)還不可用,因?yàn)榈?1 個(gè)模塊正在忙著導(dǎo)入第 2 個(gè)模塊。

在這種情況下,如果第二個(gè)模塊僅用于一個(gè)函數(shù),則可以輕松地將模塊導(dǎo)入語(yǔ)句移動(dòng)到該函數(shù)中。調(diào)用導(dǎo)入時(shí),第一個(gè)模塊將完成初始化,第二個(gè)模塊可以進(jìn)行導(dǎo)入。

如果某些模塊是特定于平臺(tái)的,則可能還需要將模塊導(dǎo)入語(yǔ)句移出頂級(jí)代碼。在這種情況下,甚至可能無(wú)法導(dǎo)入文件頂部的所有模塊。在這種情況下,在相應(yīng)的特定于平臺(tái)的代碼中導(dǎo)入正確的模塊是一個(gè)很好的選擇。

只有當(dāng)需要解決諸如避免循環(huán)導(dǎo)入或試圖減少模塊初始化時(shí)間的問(wèn)題時(shí),才可以將導(dǎo)入移動(dòng)到本地范圍,例如在函數(shù)定義中。如果根據(jù)程序的執(zhí)行方式,許多導(dǎo)入是不必要的,這種技術(shù)尤其有用。如果僅在某個(gè)函數(shù)中使用模塊,您還可能希望將導(dǎo)入移到該函數(shù)中。請(qǐng)注意,第一次加載模塊可能會(huì)因?yàn)槟K的一次初始化而代價(jià)高昂,但多次加載模塊實(shí)際上是免費(fèi)的,只需進(jìn)行幾次字典查找。即使模塊名稱(chēng)超出了作用域,模塊也可能在 sys.modules 中可用。

為什么對(duì)象之間會(huì)共享默認(rèn)值??

這種類(lèi)型的缺陷通常會(huì)惹惱新手程序員。考慮這個(gè)函數(shù)

def foo(mydict={}):  # Danger: shared reference to one dict for all calls
    ... compute something ...
    mydict[key] = value
    return mydict

第一次調(diào)用此函數(shù)時(shí),mydict 包含一項(xiàng)。第二次,mydict 包含兩項(xiàng),因?yàn)楫?dāng) foo() 開(kāi)始執(zhí)行時(shí), mydict 中已經(jīng)有一項(xiàng)了。

函數(shù)調(diào)用經(jīng)常被期望為默認(rèn)值創(chuàng)建新的對(duì)象。 但實(shí)際情況并非如此。 默認(rèn)值會(huì)在函數(shù)定義時(shí)一次性地創(chuàng)建。 如果對(duì)象發(fā)生改變,就如本示例中的字典那樣,則對(duì)函數(shù)的后續(xù)調(diào)用將會(huì)引用這個(gè)被改變的對(duì)象。

按照定義,不可變對(duì)象例如數(shù)字、字符串、元組和 None 因?yàn)椴豢勺兯允前踩摹?對(duì)可變對(duì)象例如字典、列表和類(lèi)實(shí)例的改變則可能造成迷惑。

由于這一特性,在編程中應(yīng)遵循的一項(xiàng)好習(xí)慣是不使用可變對(duì)象作為默認(rèn)值。 而應(yīng)使用 None 作為默認(rèn)值和函數(shù)中的值,檢查值為 None 的形參并創(chuàng)建相應(yīng)的列表、字典或其他可變對(duì)象。 例如,不要這樣寫(xiě):

def foo(mydict={}):
    ...

而要這樣寫(xiě):

def foo(mydict=None):
    if mydict is None:
        mydict = {}  # create a new dict for local namespace

這一特性有時(shí)會(huì)很有用處。 當(dāng)你有一個(gè)需要進(jìn)行大量耗時(shí)計(jì)算的函數(shù)時(shí),一個(gè)常見(jiàn)技巧是將每次調(diào)用函數(shù)的參數(shù)和結(jié)果值緩存起來(lái),并在同樣的值被再次請(qǐng)求時(shí)返回緩存的值。 這稱(chēng)為“記憶”,具體實(shí)現(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

你也可以使用包含一個(gè)字典的全局變量而不使用參數(shù)默認(rèn)值;這完全取決于個(gè)人偏好。

如何將可選參數(shù)或關(guān)鍵字參數(shù)從一個(gè)函數(shù)傳遞到另一個(gè)函數(shù)??

使用函數(shù)參數(shù)列表中的 *** 說(shuō)明符收集參數(shù);這會(huì)將位置參數(shù)作為元組,將關(guān)鍵字參數(shù)作為字典。然后,您可以使用 *** 調(diào)用另一個(gè)函數(shù)時(shí)傳遞這些參數(shù):

def f(x, *args, **kwargs):
    ...
    kwargs['width'] = '14.3c'
    ...
    g(x, *args, **kwargs)

形參和實(shí)參之間有什么區(qū)別??

形參 是指出現(xiàn)在函數(shù)定義中的名稱(chēng),而 實(shí)參 則是在調(diào)用函數(shù)時(shí)實(shí)際傳入的值。 形參定義了一個(gè)函數(shù)能接受何種類(lèi)型的實(shí)參。 例如,對(duì)于以下函數(shù)定義:

def func(foo, bar=None, **kwargs):
    pass

foo, barkwargsfunc 的形參。 但是,在調(diào)用 func 時(shí),例如:

func(42, bar=314, extra=somevar)

實(shí)際的值 42, 314somevar 則是實(shí)參。

為什么更改列表 'y' 也會(huì)更改列表 'x'??

如果你編寫(xiě)的代碼就像下面一樣:

>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]

你可能想知道為什么追加一個(gè)元素也改變了x。

產(chǎn)生這種結(jié)果有兩個(gè)因素:

  1. 變量只是指向具體對(duì)象的名稱(chēng)。 執(zhí)行 y = x 并不會(huì)為列表創(chuàng)建一個(gè)副本 —— 它只是創(chuàng)建了一個(gè)新變量 y 指向 x 所指向的同一對(duì)象。 這意味著只存在一個(gè)對(duì)象(列表),xy 都是對(duì)它的引用。

  2. 列表屬于 mutable 對(duì)象,這意味著你可以改變它的內(nèi)容。

在調(diào)用 append() 之后,這個(gè)可變對(duì)象的內(nèi)容由 [] 變?yōu)?[10]。 由于兩個(gè)變量都指向同一對(duì)象,因此使用任何一個(gè)名稱(chēng)所訪(fǎng)問(wèn)到的都是修改后的值 [10]

如果我們改為將不可變對(duì)象賦值給 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

我們可以看到在此情況下 xy 就不再相等了。 這是因?yàn)檎麛?shù)是 immutable 對(duì)象,當(dāng)我們執(zhí)行 x = x + 1 時(shí)我們并不是改變了 5 這個(gè)對(duì)象的值;而是創(chuàng)建了一個(gè)新的對(duì)象 (整數(shù) 6) 并將其賦值給 x (也就是改變了 x 所指向的對(duì)象)。 在賦值之后我們就有了兩個(gè)對(duì)象 (整數(shù) 65) 以及分別指向它們的兩個(gè)變量 (x 現(xiàn)在指向 6y 仍然指向 5)。

某些操作 (例如 y.append(10)y.sort()) 是改變?cè)瓕?duì)象,而看上去相似的另一些操作 (例如 y = y + [10]sorted(y)) 則是創(chuàng)建新對(duì)象。 通常在 Python 中 (以及在標(biāo)準(zhǔn)庫(kù)的所有代碼中) 會(huì)改變?cè)瓕?duì)象的方法將返回 None 以幫助避免混淆這兩種不同類(lèi)型的操作。 因此如果你錯(cuò)誤地使用了 y.sort() 并期望它將返回一個(gè)經(jīng)過(guò)排序的 y 的副本,你得到的結(jié)果將會(huì)是 None,這將導(dǎo)致你的程序產(chǎn)生一個(gè)容易診斷的錯(cuò)誤。

但是,還存在一類(lèi)操作,不同的類(lèi)型執(zhí)行相同的操作會(huì)有不同的行為:那就是增強(qiáng)賦值運(yùn)算符。 例如,+= 會(huì)原地改變列表,但不會(huì)改變?cè)M或整數(shù) (a_list += [1, 2, 3]a_list.extend([1, 2, 3]) 一樣都會(huì)改變 a_list,而 some_tuple += (1, 2, 3)some_int += 1 則會(huì)創(chuàng)建新的對(duì)象)。

換而言之:

  • 如果我們有一個(gè)可變對(duì)象 (list, dict, set 等等),我們可以使用某些特定的操作來(lái)改變它,所有指向它的變量都會(huì)顯示它的改變。

  • 如果我們有一個(gè)不可變對(duì)象 (str, int, tuple 等等),所有指向它的變量都將顯示相同樣的值,但凡是會(huì)改變這個(gè)值的操作將總是返回一個(gè)新對(duì)象。

如果你想知道兩個(gè)變量是否指向相同的對(duì)象,你可以使用 is 運(yùn)算符,或內(nèi)置函數(shù) id()

如何編寫(xiě)帶輸出參數(shù)的函數(shù)(通過(guò)引用調(diào)用)??

請(qǐng)記住在 Python 中參數(shù)是通過(guò)賦值來(lái)傳遞的。 由于賦值只是創(chuàng)建了對(duì)象的引用,因此在調(diào)用者和被調(diào)用者的參數(shù)名稱(chēng)之間沒(méi)有別名,所以本身是沒(méi)有按引用調(diào)用的。 你可以通過(guò)多種方式實(shí)現(xiàn)所需的效果。

  1. 通過(guò)返回一個(gè)結(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
    

    這幾乎總是最清晰明了的解決方案。

  2. 通過(guò)使用全局變量。 這種方式不是線(xiàn)程安全的,而且也不受推薦。

  3. 通過(guò)傳遞一個(gè)可變 (即可原地修改的) 對(duì)象:

    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
    
  4. 通過(guò)傳遞一個(gè)會(huì)被改變的字典:

    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'])
    
  5. 或者在一個(gè)類(lèi)實(shí)例中捆綁值:

    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)
    

    幾乎沒(méi)有任何適當(dāng)理由將問(wèn)題如此復(fù)雜化。

你的最佳選擇是返回一個(gè)包含多個(gè)結(jié)果的元組。

如何在Python中創(chuàng)建高階函數(shù)??

你有兩種選擇:使用嵌套作用域,或者使用可調(diào)用對(duì)象。 例如,假設(shè)你想要定義 linear(a,b) 使其返回一個(gè)函數(shù) f(x) 來(lái)設(shè)計(jì) a*x+b 的值。 可以使用以下嵌套作用域:

def linear(a, b):
    def result(x):
        return a * x + b
    return result

或使用一個(gè)可調(diào)用對(duì)象:

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)

都會(huì)給出一個(gè)可調(diào)用對(duì)象,使得 taxes(10e6) == 0.3 * 10e6 + 2.

可調(diào)用對(duì)象方式的缺點(diǎn)是速度略慢且生成的代碼略長(zhǎng)。 但是,請(qǐng)注意一組可調(diào)用對(duì)象能夠通過(guò)繼承來(lái)共享簽名:

class exponential(linear):
    # __init__ inherited
    def __call__(self, x):
        return self.a * (x ** self.b)

對(duì)象可以封裝多個(gè)方法的狀態(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)為共享同一計(jì)數(shù)變量的多個(gè)函數(shù)。

如何在Python中復(fù)制對(duì)象??

一般來(lái)說(shuō),通常情況下請(qǐng)嘗試 copy.copy()copy.deepcopy()。 不是所有對(duì)象都可以復(fù)制,但多數(shù)都是可以的。

某些對(duì)象可以方便地復(fù)制。 例如字典具有 copy() 方法:

newdict = olddict.copy()

序列可以通過(guò)切片來(lái)復(fù)制:

new_l = l[:]

如何找到對(duì)象的方法或?qū)傩裕?/a>?

對(duì)于一個(gè)用戶(hù)自定義類(lèi)的實(shí)例 x,dir(x) 將返回一個(gè)按字母順序排序的包含實(shí)例屬性和方法及其類(lèi)所定義的屬性名稱(chēng)的列表。

我的代碼如何才能發(fā)現(xiàn)對(duì)象的名稱(chēng)??

通常來(lái)說(shuō)是做不到的,因?yàn)閷?duì)象并不真正具有名稱(chēng)。 在本質(zhì)上,賦值總是會(huì)將一個(gè)名稱(chēng)綁定到某個(gè)值;defclass 語(yǔ)句也是如此,但在這種情況下該值是一個(gè)可調(diào)用對(duì)象。 考慮以下代碼:

>>> class A:
...     pass
...
>>> B = A
>>> a = B()
>>> b = a
>>> print(b)
<__main__.A object at 0x16D07CC>
>>> print(a)
<__main__.A object at 0x16D07CC>

不嚴(yán)謹(jǐn)?shù)刂v,該類(lèi)有一個(gè)名稱(chēng):雖然它是綁定了兩個(gè)名稱(chēng)并通過(guò)名稱(chēng) B 發(fā)起調(diào)用,所創(chuàng)建的實(shí)例仍然被視為類(lèi) A 的一個(gè)實(shí)例。 但是實(shí)例的名稱(chēng)則無(wú)法確定地說(shuō)是 a 或是 b,因?yàn)橛袃蓚€(gè)名稱(chēng)被綁定到了同一個(gè)值。

一般來(lái)說(shuō)你的代碼應(yīng)該沒(méi)有必要“知道”特定值的名稱(chēng)。 除非你是在編寫(xiě)特殊的內(nèi)省程序,出現(xiàn)這樣的問(wèn)題通常表明如果改變方式可能會(huì)更有利。

在 comp.lang.python 中,F(xiàn)redrik Lundh 在回答這樣的問(wèn)題時(shí)曾經(jīng)給出過(guò)一個(gè)絕佳的類(lèi)比:

跟你找出在你家門(mén)廊見(jiàn)到的某只貓的名字所用的辦法一樣:貓(對(duì)象)自己無(wú)法告訴你它的名字,它根本就不在乎 —— 所以找出它叫什么名字的唯一辦法是問(wèn)你的所有鄰居(命名空間)那是不是他們的貓(對(duì)象)……

……并且如果你發(fā)現(xiàn)它有很多名字或根本沒(méi)有名字也不必覺(jué)得驚訝!

逗號(hào)運(yùn)算符的優(yōu)先級(jí)是什么??

逗號(hào)在 Python 中不是運(yùn)算符。 考慮這個(gè)例子:

>>> "a" in "b", "a"
(False, 'a')

由于逗號(hào)不是運(yùn)算符而是表達(dá)式之間的分隔符,以上代碼的含義就相當(dāng)于:

("a" in "b"), "a"

而不是:

"a" in ("b", "a")

對(duì)于各種賦值運(yùn)算符 (=, += 等) 來(lái)說(shuō)同樣如此。 它們并不是真正的運(yùn)算符而是賦值語(yǔ)句中的語(yǔ)法分隔符。

是否有與 C 的 "?:" 三目運(yùn)算符等價(jià)的東西??

有的。 相應(yīng)語(yǔ)法如下:

[on_true] if [expression] else [on_false]

x, y = 50, 25
small = x if x < y else y

在 Python 2.5 引入此語(yǔ)法之前,常見(jiàn)的做法是使用邏輯運(yùn)算符:

[expression] and [on_true] or [on_false]

然而這種做法并不保險(xiǎn),因?yàn)楫?dāng) on_true 具有布爾假值時(shí)將會(huì)給出錯(cuò)誤的結(jié)果。 所以,使用 ... if ... else ... 形式總是會(huì)更好。

是否可以用Python編寫(xiě)混淆的單行程序??

可以。通常是在 lambda 中嵌套 lambda 來(lái)實(shí)現(xiàn)的。請(qǐng)參閱以下三個(gè)來(lái)自 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

請(qǐng)不要在家里嘗試,騷年!

函數(shù)參數(shù)列表中的斜杠(/)是什么意思??

函數(shù)參數(shù)列表中的斜杠表示在它之前的形參是僅限位置形參。 僅限位置形參沒(méi)有外部可用的名稱(chēng)。 在調(diào)用接受僅限位置形參的函數(shù)時(shí),參數(shù)只會(huì)基于它們的位置被映射到形參。 例如,pow() 是一個(gè)接受僅限位置形參的函數(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.

在形參列表末尾的斜杠意味著所有三個(gè)形參都是僅限位置形參。 因此,附帶關(guān)鍵字參數(shù)調(diào)用 pow() 將會(huì)導(dǎo)致報(bào)錯(cuò):

>>> pow(x=3, y=4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: pow() takes no keyword arguments

請(qǐng)注意目前此特性只記錄于文檔而不是有效的 Python 語(yǔ)法,不過(guò)已經(jīng)有 PEP 570 提出了 Python 中使用僅位置形參的語(yǔ)法。

數(shù)字和字符串?

如何指定十六進(jìn)制和八進(jìn)制整數(shù)??

要指定一個(gè)八進(jìn)制數(shù)碼,則在八進(jìn)制值之前加一個(gè)零和一個(gè)小寫(xiě)或大寫(xiě)字母 "o" 作為前綴。 例如,要將變量 "a" 設(shè)為八進(jìn)制的 "10" (十進(jìn)制的 8),就輸入:

>>> a = 0o10
>>> a
8

十六進(jìn)制數(shù)也同樣簡(jiǎn)單。 只要在十六進(jìn)制數(shù)之前加一個(gè)零和一個(gè)小寫(xiě)或大寫(xiě)字母 "x"。 十六進(jìn)制數(shù)碼中的字母可以為大寫(xiě)或小寫(xiě)。 例如在 Python 解釋器中輸入:

>>> a = 0xa5
>>> a
165
>>> b = 0XB2
>>> b
178

為什么-22 // 10返回-3??

這主要是為了讓 i % j 的正負(fù)與 j 一致,如果你想要這樣的結(jié)果,并且又想要:

i == (i // j) * j + (i % j)

那么整除就必須向下取整。 C 同樣要求保持一致,并且編譯器在截短 i // j 的結(jié)果值時(shí)需要使 i % j 的正負(fù)與 i 一致。

對(duì)于 i % j 來(lái)說(shuō) j 為負(fù)值的應(yīng)用場(chǎng)景實(shí)際上是非常少的。 而 j 為正值的情況則非常多,并且實(shí)際上在所有情況下讓 i % j 的結(jié)果為 >= 0 會(huì)更有用處。 如果如果現(xiàn)在時(shí)間為 10 時(shí),那么 200 小時(shí)前應(yīng)是幾時(shí)? -190 % 12 == 2 是有用處的;-190 % 12 == -10 則是會(huì)導(dǎo)致意外的漏洞。

如何將字符串轉(zhuǎn)換為數(shù)字??

對(duì)于整數(shù),可使用內(nèi)置的 int() 類(lèi)型構(gòu)造器,例如 int('144') == 144。 類(lèi)似地,可使用 float() 轉(zhuǎn)換為浮點(diǎn)數(shù),例如 float('144') == 144.0

默認(rèn)情況下,這些操作會(huì)將數(shù)字按十進(jìn)制來(lái)解讀,因此 int('0144') == 144int('0x144') 會(huì)引發(fā) ValueErrorint(string, base) 接受第二個(gè)可選參數(shù)指定轉(zhuǎn)換的基數(shù),例如 int('0x144', 16) == 324。 如果指定基數(shù)為 0,則按 Python 規(guī)則解讀數(shù)字:前綴 '0o' 表示八進(jìn)制,而 '0x' 表示十六進(jìn)制。

如果你只是想將字符串轉(zhuǎn)為數(shù)字,請(qǐng)不要使用內(nèi)置函數(shù) eval()eval() 的速度會(huì)慢很多并且有安全風(fēng)險(xiǎn):別人可能會(huì)傳入具有你不想要的附帶效果的 Python 表達(dá)式。 例如,別人可以傳入 __import__('os').system("rm -rf $HOME") 這將刪除你的家目錄。

eval() 還具有將數(shù)字解讀為 Python 表達(dá)式的效果,這樣 eval('09') 將會(huì)導(dǎo)致語(yǔ)法錯(cuò)誤,因?yàn)?Python 不允許十進(jìn)制數(shù)的首位是 '0' ('0' 除外)。

如何將數(shù)字轉(zhuǎn)換為字符串??

例如要將數(shù)字 144 轉(zhuǎn)換為字符串 '144',可使用內(nèi)置類(lèi)型構(gòu)造器 str()。 如果想要表示為十六進(jìn)制或八進(jìn)制數(shù),可使用內(nèi)置函數(shù) hex()oct()。 想要更好地格式化,請(qǐng)參閱 格式化字符串字面值格式字符串語(yǔ)法 等小節(jié),例如 "{:04d}".format(144) 生成 '0144'"{:.3f}".format(1.0/3.0) 生成 '0.333'

如何修改字符串??

無(wú)法修改,因?yàn)樽址遣豢勺儗?duì)象。 在大多數(shù)情況下,你應(yīng)該使用你想要的各種部分來(lái)構(gòu)造一個(gè)新字符串。 但是,如果你想要一個(gè)可以原地修改 Unicode 數(shù)據(jù)的對(duì)象,可嘗試使用 io.StringIO 對(duì)象或 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ù)/方法??

有多種技巧可供選擇。

  • 最好的做法是使用一個(gè)將字符串映射到函數(shù)的字典。 這一技巧的主要優(yōu)勢(shì)在于字符串不必與函數(shù)名稱(chēng)一致。 這也是用于模擬其他語(yǔ)言中 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')()
    

    請(qǐng)注意 getattr() 可用于任何對(duì)象,包括類(lèi)、類(lèi)實(shí)例、模塊等等。

    在標(biāo)準(zhǔn)庫(kù)中多次使用了這個(gè)技巧,例如:

    class Foo:
        def do_foo(self):
            ...
    
        def do_bar(self):
            ...
    
    f = getattr(foo_instance, 'do_' + opname)
    f()
    
  • 使用 locals()eval() 來(lái)解析出函數(shù)名:

    def myFunc():
        print("hello")
    
    fname = "myFunc"
    
    f = locals()[fname]
    f()
    
    f = eval(fname)
    f()
    

    注意:使用 eval() 速度慢而且危險(xiǎn)。 如果你不能絕對(duì)掌控字符串的內(nèi)容,別人將能傳入可被解析為任意函數(shù)直接執(zhí)行的字符串。

是否有與Perl 的chomp() 等效的方法,用于從字符串中刪除尾隨換行符??

可以使用 S.rstrip("\r\n") 從字符串 S 的末尾刪除所有的換行符,而不刪除其他尾隨空格。如果字符串 S 表示多行,且末尾有幾個(gè)空行,則將刪除所有空行的換行符:

>>> lines = ("line 1 \r\n"
...          "\r\n"
...          "\r\n")
>>> lines.rstrip("\n\r")
'line 1 '

由于通常只在一次讀取一行文本時(shí)才需要這樣做,所以使用 S.rstrip() 這種方式工作得很好。

是否有 scanf() 或 sscanf() 的對(duì)應(yīng)物??

沒(méi)有這樣的對(duì)應(yīng)物。

對(duì)于簡(jiǎn)單的輸入解析,最方便的做法通常是使用字符串對(duì)象的 split() 方法將一行內(nèi)容拆解為以空格分隔的單詞,然后使用 int()float() 將表示十進(jìn)制數(shù)的字符串轉(zhuǎn)換為數(shù)值。 split() 支持可選的 "sep" 形參,適用于內(nèi)容行使用空格符以外的分隔符的情況。

以于更復(fù)雜的輸入解析,正則表達(dá)式會(huì)比 C 的 sscanf() 更強(qiáng)大,也更適合此類(lèi)任務(wù)。

性能?

我的程序太慢了。該如何加快速度??

總的來(lái)說(shuō),這是個(gè)棘手的問(wèn)題。首先,下面列出了深入了解前需要記住的事情:

  • 不同的 Python 實(shí)現(xiàn)具有不同的性能特點(diǎn)。 本 FAQ 著重解答的是 CPython

  • 行為可能因操作系統(tǒng)而異,尤其是在談?wù)?I / O 或多線(xiàn)程時(shí)。

  • 在嘗試優(yōu)化任何代碼 ,應(yīng)始終找到程序中的熱點(diǎn)(請(qǐng)參閱 profile 模塊)。

  • 編寫(xiě)基準(zhǔn)腳本將允許您在搜索改進(jìn)時(shí)快速迭代(請(qǐng)參閱 timeit 模塊)。

  • 強(qiáng)烈建議在可能引入隱藏在復(fù)雜優(yōu)化中的回歸之前,要有良好的代碼覆蓋率(通過(guò)單元測(cè)試或任何其他技術(shù))。

話(huà)雖如此,加速Python代碼有很多技巧。以下是一些可以達(dá)到可接受的性能水平的一般原則:

  • 使您的算法更快(或更改為更快的算法)可以產(chǎn)生比嘗試在代碼中使用微優(yōu)化技巧更大的好處。

  • 使用正確的數(shù)據(jù)結(jié)構(gòu)。參考文檔 內(nèi)置類(lèi)型collections 模塊。

  • 當(dāng)標(biāo)準(zhǔn)庫(kù)提供用于執(zhí)行某些操作的原語(yǔ)時(shí),可能(盡管不能保證)比您可能提出的任何替代方案更快。對(duì)于用C編寫(xiě)的原語(yǔ),例如內(nèi)置函數(shù)和一些擴(kuò)展類(lèi)型,這是真的。例如,請(qǐng)確保使用 list.sort() 內(nèi)置方法或相關(guān)的 sorted() 函數(shù)進(jìn)行排序(有關(guān)適度高級(jí)用法的示例,請(qǐng)參閱 排序指南 )。

  • 抽象傾向于創(chuàng)造間接性并迫使翻譯更多地工作。如果間接級(jí)別超過(guò)完成的有用工作量,則程序?qū)⒆兟D銘?yīng)該避免過(guò)度抽象,特別是在微小的功能或方法的形式下(這通常也會(huì)對(duì)可讀性產(chǎn)生不利影響)。

如果你已經(jīng)達(dá)到純 Python 允許的限制,那么有一些工具可以讓你走得更遠(yuǎn)。 例如, Cython 可以將稍微修改的 Python 代碼版本編譯為 C 擴(kuò)展,并且可以在許多不同的平臺(tái)上使用。 Cython 可以利用編譯(和可選的類(lèi)型注釋?zhuān)﹣?lái)使代碼明顯快于解釋運(yùn)行時(shí)的速度。 如果您對(duì) C 編程技能有信心,也可以自己 編寫(xiě) C 擴(kuò)展模塊

參見(jiàn)

專(zhuān)門(mén)介紹 性能提示 的wiki頁(yè)面。

將多個(gè)字符串連接在一起的最有效方法是什么??

strbytes 對(duì)象是不可變的,因此將多個(gè)字符串連接在一起效率很低,因?yàn)槊總€(gè)連接都會(huì)創(chuàng)建一個(gè)新對(duì)象。在一般情況下,總運(yùn)行時(shí)間是總字符串長(zhǎng)度的二次方。

要連接多個(gè) str 對(duì)象,通常推薦的用法是將它們放入一個(gè)列表中并在結(jié)尾處調(diào)用 str.join()

chunks = []
for s in my_strings:
    chunks.append(s)
result = ''.join(chunks)

(另一個(gè)合理有效的慣用方法是 io.StringIO

要連接多個(gè) str 對(duì)象,建議使用本地連接( += 運(yùn)算符)擴(kuò)展 bytearray 對(duì)象:

result = bytearray()
for b in my_bytes_objects:
    result += b

序列(元組/列表)?

如何在元組和列表之間進(jìn)行轉(zhuǎn)換??

類(lèi)型構(gòu)造器 tuple(seq) 可將任意序列(實(shí)際上是任意可迭代對(duì)象)轉(zhuǎn)換為具有相同排列順序的相同條目的元組。

例如,tuple([1, 2, 3]) 產(chǎn)生 (1, 2, 3)tuple('abc') 產(chǎn)生 ('a', 'b', 'c')。 如果參數(shù)為一個(gè)元組,它不會(huì)創(chuàng)建副本而是返回同一對(duì)象,因此如果你不確定某個(gè)對(duì)象是否為元組時(shí)也可簡(jiǎn)單地調(diào)用 tuple()

類(lèi)型構(gòu)造器 list(seq) 可將任意序列或可迭代對(duì)象轉(zhuǎn)換為具有相同排列順序的相同條目的列表。 例如,list((1, 2, 3)) 產(chǎn)生 [1, 2, 3]list('abc') 產(chǎn)生 ['a', 'b', 'c']。 如果參數(shù)為一個(gè)列表,它會(huì)像 seq[:] 那樣創(chuàng)建一個(gè)副本。

什么是負(fù)數(shù)序號(hào)??

Python 序列使用正數(shù)或負(fù)數(shù)作為序號(hào)或稱(chēng)索引號(hào)。 對(duì)于正數(shù)序號(hào),第一個(gè)序號(hào)為 0 而 1 為第二個(gè)序號(hào),依此類(lèi)推。 對(duì)于負(fù)數(shù)序號(hào),倒數(shù)第一個(gè)序號(hào)為 -1 而倒數(shù)第二個(gè)序號(hào)為 -2,依此類(lèi)推。 可以認(rèn)為 seq[-n] 就相當(dāng)于 seq[len(seq)-n]

使用負(fù)數(shù)序號(hào)有時(shí)會(huì)很方便。 例如 S[:-1] 就是原字符串去掉最后一個(gè)字符,這可以用來(lái)移除某個(gè)字符串末尾的換行符。

如何以相反的順序迭代序列??

使用 reversed() 內(nèi)置函數(shù),這是Python 2.4中的新功能:

for x in reversed(sequence):
    ...  # do something with x ...

這不會(huì)修改您的原始序列,而是構(gòu)建一個(gè)反向順序的新副本以進(jìn)行迭代。

在 Python 2.3 里,您可以使用擴(kuò)展切片語(yǔ)法:

for x in sequence[::-1]:
    ...  # do something with x ...

如何從列表中刪除重復(fù)項(xiàng)??

有關(guān)執(zhí)行此操作的許多方法的詳細(xì)討論,請(qǐng)參閱 Python Cookbook:

如果您不介意重新排序列表,請(qǐng)對(duì)其進(jìn)行排序,然后從列表末尾進(jìn)行掃描,刪除重復(fù)項(xiàng):

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 ),這通常會(huì)更快:

mylist = list(set(mylist))

這會(huì)將列表轉(zhuǎn)換為集合,從而刪除重復(fù)項(xiàng),然后返回到列表中。

如何在Python中創(chuàng)建數(shù)組??

使用列表:

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

列表在時(shí)間復(fù)雜度方面相當(dāng)于C或Pascal數(shù)組;主要區(qū)別在于,python列表可以包含許多不同類(lèi)型的對(duì)象。

array 模塊還提供了創(chuàng)建具有緊湊表示的固定類(lèi)型的數(shù)組的方法,但它的索引速度比列表慢。還要注意,數(shù)字?jǐn)U展和其他擴(kuò)展還定義了具有各種特性的類(lèi)似數(shù)組的結(jié)構(gòu)。

要獲取Lisp樣式的列表,可以使用元組模擬cons單元:

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

如果需要可變性,可以使用列表而不是元組。這里模擬lisp car的是 lisp_list[0] ,模擬cdr的是 lisp_list[1] 。只有在你確定真的需要的時(shí)候才這樣做,因?yàn)樗ǔ1仁褂肞ython列表慢得多。

如何創(chuàng)建多維列表??

你可能試圖制作一個(gè)像這樣的多維數(shù)組:

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

如果你打印它,看起來(lái)是正確的:

>>> A
[[None, None], [None, None], [None, None]]

但是,當(dāng)你給某一項(xiàng)賦值時(shí),會(huì)同時(shí)在多個(gè)位置顯示變化:

>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]

其中的原因在于使用 * 對(duì)列表執(zhí)行重復(fù)操作并不是創(chuàng)建副本,它只是創(chuàng)建現(xiàn)有對(duì)象的引用。 *3 創(chuàng)建了對(duì)長(zhǎng)度為二的同一列表的 3 個(gè)引用。 對(duì)某一行的改變會(huì)作用于所有行,通常這一定不是你所希望的。

建議的做法是先創(chuàng)建一個(gè)所需長(zhǎng)度的列表,然后其中的元素再以一個(gè)新創(chuàng)建的列表來(lái)填充:

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

這樣就生成了一個(gè)包含 3 個(gè)長(zhǎng)度為二的不同列表的列表。 你也可以使用列表推導(dǎo)式:

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

或者你還可以使用提供矩陣類(lèi)型的擴(kuò)展包;其中最著名的是 NumPy

如何將方法應(yīng)用于一系列對(duì)象??

可以使用列表推導(dǎo)式:

result = [obj.method() for obj in mylist]

為什么 a_tuple[i] += ['item'] 會(huì)在執(zhí)行加法時(shí)引發(fā)異常??

這是由兩個(gè)事實(shí)共同導(dǎo)致的結(jié)果,一是增強(qiáng)賦值運(yùn)算符屬于 賦值 運(yùn)算符,二是在 Python 中存在可變和不可變兩種不同的對(duì)象。

此處的討論在任何對(duì)元組中指向可變對(duì)象的元素使用增強(qiáng)賦值運(yùn)算符的情況都是普遍成立的,但在此我們只以 list+= 來(lái)舉例。

如果你寫(xiě)成這樣:

>>> a_tuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (most recent call last):
   ...
TypeError: 'tuple' object does not support item assignment

發(fā)生異常的原因是顯而易見(jiàn)的: 1 會(huì)與對(duì)象 a_tuple[0] 相加,而該對(duì)象為 (1),得到結(jié)果對(duì)象 2,但當(dāng)我們?cè)噲D將運(yùn)算結(jié)果 2 賦值給元組的 0 號(hào)元素時(shí)就將報(bào)錯(cuò),因?yàn)槲覀儾荒芨淖冊(cè)M的元素所指向的對(duì)象。

在表層之處,以上增強(qiáng)賦值語(yǔ)句所做的大致是這樣:

>>> result = a_tuple[0] + 1
>>> a_tuple[0] = result
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

由于元組是不可變的,因此操作的賦值部分會(huì)引發(fā)錯(cuò)誤。

當(dāng)你這樣寫(xiě)的時(shí)候:

>>> a_tuple = (['foo'], 'bar')
>>> a_tuple[0] += ['item']
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

發(fā)生異常會(huì)令人略感吃驚,還有一個(gè)更為令人吃驚的事實(shí):雖然有報(bào)錯(cuò),但是添加操作卻生效了:

>>> a_tuple[0]
['foo', 'item']

要明白為何會(huì)這樣,你需要知道 (a) 如果一個(gè)對(duì)象實(shí)現(xiàn)了 __iadd__ 魔術(shù)方法,它會(huì)在執(zhí)行 += 增強(qiáng)賦值時(shí)被調(diào)用,并且其返回值將用于該賦值語(yǔ)句; (b) 對(duì)于列表來(lái)說(shuō),__iadd__ 等價(jià)于在列表上調(diào)用 extend 并返回該列表。 因此對(duì)于列表我們可以說(shuō) += 就是 list.extend 的“快捷方式”:

>>> a_list = []
>>> a_list += [1]
>>> a_list
[1]

這相當(dāng)于:

>>> result = a_list.__iadd__([1])
>>> a_list = result

a_list 所引用的對(duì)象已被修改,而引用被修改對(duì)象的指針又重新被賦值給 a_list。 賦值的最終結(jié)果沒(méi)有變化,因?yàn)樗且?a_list 之前所引用的同一對(duì)象的指針,但仍然發(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í)行,因此列表得到了擴(kuò)充,但是雖然 result 指向了 a_tuple[0] 已經(jīng)指向的同一對(duì)象,最后的賦值仍然導(dǎo)致了報(bào)錯(cuò),因?yàn)樵M是不可變的。

我想做一個(gè)復(fù)雜的排序:你能用Python做一個(gè)Schwartzian變換嗎??

該技術(shù)歸功于Perl社區(qū)的 Randal Schwartz,它通過(guò)將每個(gè)元素映射到其 "排序值(sort value)" 的度量對(duì)列表中的元素進(jìn)行排序。在Python中,使用 list.sort() 方法的 key 參數(shù):

Isorted = L[:]
Isorted.sort(key=lambda s: int(s[10:15]))

如何按其他列表中的值對(duì)一個(gè)列表進(jìn)行排序??

將它們合并到元組的迭代器中,對(duì)結(jié)果列表進(jìn)行排序,然后選擇所需的元素。

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

如果你覺(jué)得這個(gè)更容易讀懂,那么你可能更喜歡使用這個(gè)而不是前面的列表推導(dǎo)。然而,對(duì)于長(zhǎng)列表來(lái)說(shuō),它的速度幾乎是原來(lái)的兩倍。為什么?首先, append() 操作必須重新分配內(nèi)存,雖然它使用了一些技巧來(lái)避免每次都這樣做,但它仍然偶爾需要這樣做,而且代價(jià)相當(dāng)高。第二,表達(dá)式 "result.append" 需要額外的屬性查找。第三,必須執(zhí)行所有這些函數(shù)調(diào)用會(huì)降低速度。

對(duì)象?

什么是類(lèi)??

類(lèi) 是通過(guò)執(zhí)行類(lèi)語(yǔ)句創(chuàng)建的特定對(duì)象類(lèi)型。類(lèi)對(duì)象 被當(dāng)作模板來(lái)創(chuàng)建實(shí)例對(duì)象,實(shí)例對(duì)象包含了特定于數(shù)據(jù)類(lèi)型的數(shù)據(jù)(屬性)和代碼(方法)。

類(lèi)可以基于一個(gè)或多個(gè)的其他類(lèi),稱(chēng)之為基類(lèi)(ES),它繼承基類(lèi)的屬性和方法,這樣就可以通過(guò)繼承來(lái)連續(xù)地細(xì)化對(duì)象模型。例如:您可能有一個(gè) Mailbox 類(lèi)提供郵箱的基本訪(fǎng)問(wèn)方法.,它的子類(lèi) MboxMailbox, MaildirMailbox, OutlookMailbox 用于處理各種特定郵箱格式。

什么是方法??

方法 實(shí)際上就是類(lèi)定義中的函數(shù)。對(duì)于某個(gè)對(duì)象 x 上的方法,通常稱(chēng)為 x.name(arguments...)

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

什么是 self ??

Self 只是 方法 的第一個(gè)參數(shù)的常規(guī)名稱(chēng)。例如:對(duì)于某個(gè)類(lèi)的某個(gè)實(shí)例 x ,其方法 meth(self, a, b, c) 實(shí)際上應(yīng)該被稱(chēng)為 x.meth(a, b, c) ;對(duì)于被調(diào)用的方法會(huì)被稱(chēng)為 meth(x, a, b, c)

另請(qǐng)參閱 為什么必須在方法定義和調(diào)用中顯式使用“self”?

如何檢查對(duì)象是否為給定類(lèi)或其子類(lèi)的一個(gè)實(shí)例??

可使用內(nèi)置函數(shù) isinstance(obj, cls)。 你可以提供一個(gè)元組而不是單個(gè)類(lèi)來(lái)檢查某個(gè)對(duì)象是否為任意多個(gè)類(lèi)當(dāng)中某一個(gè)類(lèi)的實(shí)例,例如 isinstance(obj, (class1, class2, ...)),也可以檢查某個(gè)對(duì)象是否為 Python 內(nèi)置類(lèi)型當(dāng)中某一個(gè)類(lèi)型的對(duì)象,例如 isinstance(obj, str)isinstance(obj, (int, float, complex))

請(qǐng)注意大多數(shù)程序不會(huì)經(jīng)常對(duì)用戶(hù)自定義類(lèi)使用 isinstance()。 如果是你自已開(kāi)發(fā)的類(lèi),更正確的面向?qū)ο箫L(fēng)格是在類(lèi)中定義方法來(lái)封裝特定的行為,而不是檢查對(duì)象的類(lèi)并根據(jù)它屬于什么類(lèi)來(lái)做不同的事。 例如,如果你有一個(gè)執(zhí)行某些操作的函數(shù):

def search(obj):
    if isinstance(obj, Mailbox):
        ...  # code to search a mailbox
    elif isinstance(obj, Document):
        ...  # code to search a document
    elif ...

更好的方法是在所有類(lèi)上定義一個(gè) 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(chēng)為設(shè)計(jì)模式)。 假設(shè)您有一個(gè)對(duì)象 x 并且想要改變其中一個(gè)方法的行為。 您可以創(chuàng)建一個(gè)新類(lèi),它提供您感興趣的方法的新實(shí)現(xiàn),并將所有其他方法委托給 x 的相應(yīng)方法。

Python程序員可以輕松實(shí)現(xiàn)委托。 例如,以下類(lèi)實(shí)現(xiàn)了一個(gè)類(lèi),該類(lèi)的行為類(lèi)似于文件,但將所有寫(xiě)入的數(shù)據(jù)轉(zhuǎn)換為大寫(xiě):

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 類(lèi)重新定義了 write() 方法在調(diào)用下層的 self._outfile.write() 方法之前將參數(shù)字符串轉(zhuǎn)換為大寫(xiě)形式。 所有其他方法都被委托給下層的 self._outfile 對(duì)象。 委托是通過(guò) __getattr__ 方法來(lái)完成的;請(qǐng)參閱 語(yǔ)言參考 了解有關(guān)控制屬性訪(fǎng)問(wèn)的更多信息。

請(qǐng)注意對(duì)于更一般的情況來(lái)說(shuō),委托可能包含更多細(xì)節(jié)問(wèn)題。 當(dāng)某些屬性既需要讀取又需要設(shè)置時(shí),類(lèi)還必須定義 __setattr__() 方法,并且這樣做必須小心謹(jǐn)慎。 __setattr__() 的基本實(shí)現(xiàn)大致相當(dāng)于以下代碼:

class X:
    ...
    def __setattr__(self, name, value):
        self.__dict__[name] = value
    ...

大多數(shù) __setattr__() 實(shí)現(xiàn)必須修改 self.__dict__ 來(lái)為自身保存局部狀態(tài)而又不至于造成無(wú)限遞歸。

如何從覆蓋基類(lèi)的派生類(lèi)調(diào)用基類(lèi)中定義的方法??

使用內(nèi)置的 super() 函數(shù):

class Derived(Base):
    def meth(self):
        super(Derived, self).meth()

對(duì)于 Python 3.0之前的版本,您可能正在使用經(jīng)典類(lèi):對(duì)于諸如 class Derived(Base): ... 之類(lèi)的類(lèi)定義,可以將在 Base (或 Base 中的一個(gè)的基類(lèi))中定義的方法 meth() 調(diào)用為 Base.meth(self, arguments...) 。這里, Base.meth 是一個(gè)未綁定的方法,因此您需要提供 self 參數(shù)。

如何組織代碼以便更改基類(lèi)??

可以為基類(lèi)定義別名,在類(lèi)定義之前為其分配實(shí)際基類(lèi),并在整個(gè)類(lèi)中使用別名。然后更改分配給別名的值,就能實(shí)現(xiàn)上述要求。順便提一下,如果你想動(dòng)態(tài)決定(例如,取決于資源的可用性)要使用哪個(gè)基類(lèi),這個(gè)技巧也很方便。例如:

BaseAlias = <real base class>

class Derived(BaseAlias):
    def meth(self):
        BaseAlias.meth(self)
        ...

如何創(chuàng)建靜態(tài)類(lèi)數(shù)據(jù)和靜態(tài)類(lèi)方法??

Python支持靜態(tài)數(shù)據(jù)和靜態(tài)方法(在C ++或Java的意義上)。

對(duì)于靜態(tài)數(shù)據(jù),只需定義一個(gè)類(lèi)屬性。要為屬性分配新值,就必須在賦值中顯式使用類(lèi)名:

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

對(duì)于任意 c 來(lái)說(shuō)只要 isinstance(c, C) 為真,則 c.count 同樣也指向 C.count,除非被 c 自身,或者從 c.__class__ 回到 C 的基類(lèi)搜索路徑上的某個(gè)類(lèi)所重載。

注意:在 C 的某個(gè)方法內(nèi)部,像 self.count = 42 這樣的賦值將在 self 自身的字典中新建一個(gè)名為 "count" 的不相關(guān)實(shí)例。 想要重新綁定類(lèi)靜態(tài)數(shù)據(jù)名稱(chēng)就必須總是指明類(lèi)名,無(wú)論是在方法內(nèi)部還是外部:

C.count = 314

靜態(tài)方法是可行的:

class C:
    @staticmethod
    def static(arg1, arg2, arg3):
        # No 'self' parameter!
        ...

然而,獲得靜態(tài)方法效果的更直接的方法是通過(guò)一個(gè)簡(jiǎn)單的模塊級(jí)函數(shù):

def getcount():
    return C.count

如果您的代碼是結(jié)構(gòu)化的,以便為每個(gè)模塊定義一個(gè)類(lèi)(或緊密相關(guān)的類(lèi)層次結(jié)構(gòu)),那么這就提供了所需的封裝。

如何在Python中重載構(gòu)造函數(shù)(或方法)??

這個(gè)答案實(shí)際上適用于所有方法,但問(wèn)題通常首先出現(xiàn)在構(gòu)造函數(shù)的上下文中。

在C ++中,你會(huì)這樣寫(xiě)

class C {
    C() { cout << "No arguments\n"; }
    C(int i) { cout << "Argument is " << i << "\n"; }
}

在Python中,您必須編寫(xiě)一個(gè)構(gòu)造函數(shù),使用默認(rèn)參數(shù)捕獲所有情況。例如:

class C:
    def __init__(self, i=None):
        if i is None:
            print("No arguments")
        else:
            print("Argument is", i)

這不完全等同,但在實(shí)踐中足夠接近。

你也可以嘗試一個(gè)可變長(zhǎng)度的參數(shù)列表,例如:

def __init__(self, *args):
    ...

相同的方法適用于所有方法定義。

我嘗試使用 __spam ,但是得到一個(gè)關(guān)于 _SomeClassName__spam 的錯(cuò)誤信息。?

以雙下劃線(xiàn)打頭的變量會(huì)被“更名”以提供一種定義類(lèi)私有變量的簡(jiǎn)單而有效的方式。 任何形式為 __spam 的標(biāo)識(shí)符(至少前綴兩個(gè)下劃線(xiàn),至多后綴一個(gè)下劃線(xiàn))文本會(huì)被替換為 _classname__spam,其中 classname 為去除了全部前綴下劃線(xiàn)的當(dāng)前類(lèi)名稱(chēng)。

這并不能保證私密性:外部用戶(hù)仍然可以訪(fǎng)問(wèn) "_classname__spam" 屬性,私有變量值也在對(duì)象的 __dict__ 中可見(jiàn)。 許多 Python 程序員從來(lái)都不使用這種私有變量名稱(chēng)。

類(lèi)定義了 __del__ 方法,但是刪除對(duì)象時(shí)沒(méi)有調(diào)用它。?

這有幾個(gè)可能的原因。

del 語(yǔ)句不一定調(diào)用 __del__() —— 它只是減少對(duì)象的引用計(jì)數(shù),如果(引用計(jì)數(shù))達(dá)到零,才會(huì)調(diào)用 __del__()

如果數(shù)據(jù)結(jié)構(gòu)包含循環(huán)鏈接(例如,每個(gè)子級(jí)都有一個(gè)父級(jí)引用,每個(gè)父級(jí)都有一個(gè)子級(jí)列表的樹(shù)),則引用計(jì)數(shù)將永遠(yuǎn)不會(huì)返回零。盡管Python 偶爾會(huì)運(yùn)行一個(gè)算法來(lái)檢測(cè)這樣的循環(huán),但在數(shù)據(jù)結(jié)構(gòu)的引用計(jì)數(shù)清零后,垃圾收集器可能需要一段時(shí)間來(lái)運(yùn)行,因此 __del__() 方法可能會(huì)在不方便和隨機(jī)的時(shí)間被調(diào)用。這對(duì)于重現(xiàn)一個(gè)問(wèn)題,是非常不方便的。更糟糕的是,對(duì)象 __del__() 的方法執(zhí)行順序是任意的。雖然可以運(yùn)行 gc.collect() 來(lái)強(qiáng)制回收,但在一些病態(tài)的情況下,對(duì)象永遠(yuǎn)不會(huì)被回收。

盡管有循環(huán)收集器,但在對(duì)象上定義一個(gè)顯式的 close() 方法以便在用完之后調(diào)用它仍然是一個(gè)好主意。 這樣 close() 方法可以隨即刪除引用子對(duì)象的屬性。 不要直接調(diào)用 __del__() —— 應(yīng)該由 __del__() 調(diào)用 close(),并且 close() 能確保可以被同一對(duì)象多次地調(diào)用。

另一種避免循環(huán)引用的方法是使用 weakref 模塊,該模塊允許您指向?qū)ο蠖辉黾悠湟糜?jì)數(shù)。例如,樹(shù)狀數(shù)據(jù)結(jié)構(gòu)應(yīng)該對(duì)其父級(jí)和同級(jí)引用使用弱引用(如果需要的話(huà)!)

最后,如果 __del__() 方法引發(fā)異常,會(huì)將警告消息打印到 sys.stderr

如何獲取給定類(lèi)的所有實(shí)例的列表??

Python不跟蹤類(lèi)(或內(nèi)置類(lèi)型)的所有實(shí)例。您可以對(duì)類(lèi)的構(gòu)造函數(shù)進(jìn)行編程,以通過(guò)保留每個(gè)實(shí)例的弱引用列表來(lái)跟蹤所有實(shí)例。

為什么 id() 的結(jié)果看起來(lái)不是唯一的??

id() 返回一個(gè)整數(shù),該整數(shù)在對(duì)象的生命周期內(nèi)保證是唯一的。因?yàn)樵贑Python中,這是對(duì)象的內(nèi)存地址,所以經(jīng)常發(fā)生在從內(nèi)存中刪除對(duì)象之后,下一個(gè)新創(chuàng)建的對(duì)象被分配在內(nèi)存中的相同位置。這個(gè)例子說(shuō)明了這一點(diǎn):

>>> id(1000) 
13901272
>>> id(2000) 
13901272

這兩個(gè)id屬于之前創(chuàng)建的不同整數(shù)對(duì)象,并在執(zhí)行 id() 調(diào)用后立即刪除。要確保要檢查其id的對(duì)象仍處于活動(dòng)狀態(tài),請(qǐng)創(chuàng)建對(duì)該對(duì)象的另一個(gè)引用:

>>> a = 1000; b = 2000
>>> id(a) 
13901272
>>> id(b) 
13891296

模塊?

如何創(chuàng)建 .pyc 文件??

當(dāng)一個(gè)模塊首次被導(dǎo)入時(shí)(或自當(dāng)前已編譯文件創(chuàng)建后源文件被修改時(shí)),將會(huì)在對(duì)應(yīng) .py 文件所在目錄的 __pycache__ 子目錄下創(chuàng)建一個(gè)包含已編譯代碼的 .pyc 文件。 該 .pyc 文件的文件名的開(kāi)頭部分將與對(duì)應(yīng) .py 文件名相同,并以 .pyc 為后綴,中間部門(mén)則是基于創(chuàng)建它的特定 python 二進(jìn)制代碼版本。 (詳情參見(jiàn) PEP 3147。)

無(wú)法創(chuàng)建 .pyc 文件的可能原因是包含源文件的目錄存在權(quán)限問(wèn)題,這意味著 __pycache__ 子目錄無(wú)法被創(chuàng)建。 舉例來(lái)說(shuō),如果你以某一用戶(hù)來(lái)開(kāi)發(fā)程序但以另一用戶(hù)身份來(lái)運(yùn)行程序時(shí)就可能發(fā)生問(wèn)題,測(cè)試 Web 服務(wù)器就屬于這種情況。

除非設(shè)置了 PYTHONDONTWRITEBYTECODE 環(huán)境變量,否則當(dāng)你導(dǎo)入模塊并且 Python 具有創(chuàng)建 __pycache__ 子目錄并將已編譯模塊寫(xiě)入該子目錄的能力(權(quán)限、存儲(chǔ)空間等等)時(shí)就會(huì)自動(dòng)創(chuàng)建 .pyc 文件。

在最高層級(jí)運(yùn)行的 Python 腳本不被視為導(dǎo)入,因此不會(huì)創(chuàng)建 .pyc 文件。 例如,如果你有一個(gè)最高層級(jí)模塊文件 foo.py,它又導(dǎo)入了另一個(gè)模塊 xyz.py,當(dāng)你運(yùn)行 foo 模塊 (通過(guò)輸入終端命令 python foo.py),則將為 xyz 創(chuàng)建一個(gè) .pyc,因?yàn)?xyz 是被導(dǎo)入的,但不會(huì)為 foo 創(chuàng)建 .pyc 文件,因?yàn)?foo.py 不是被導(dǎo)入的。

如果你需要為 foo 創(chuàng)建 .pyc 文件 —— 即為不是被導(dǎo)入的模塊創(chuàng)建 .pyc 文件 —— 你可以使用 py_compilecompileall 模塊。

py_compile 模塊能夠手動(dòng)編譯任意模塊。 一種做法是交互式地使用該模塊中的 compile() 函數(shù):

>>> import py_compile
>>> py_compile.compile('foo.py')                 

這將會(huì)將 .pyc 文件寫(xiě)入與 foo.py 相同位置下的 __pycache__ 子目錄(或者你也可以通過(guò)可選參數(shù) cfile 來(lái)重載該行為)。

你還可以使用 compileall 模塊自動(dòng)編譯一個(gè)目錄或多個(gè)目錄下的所有文件。 具體做法可以是在命令行提示符中運(yùn)行 compileall.py 并提供包含要編譯 Python 文件的目錄路徑:

python -m compileall .

如何找到當(dāng)前模塊名稱(chēng)??

模塊可以通過(guò)查看預(yù)定義的全局變量 __name__ 找到自己的模塊名稱(chēng)。如果它的值為 '__main__' ,程序?qū)⒆鳛槟_本運(yùn)行。通常,通過(guò)導(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

問(wèn)題是解釋器將執(zhí)行以下步驟:

  • 首先導(dǎo)入foo

  • 創(chuàng)建用于foo的空全局變量

  • foo被編譯并開(kāi)始執(zhí)行

  • foo 導(dǎo)入 bar

  • 創(chuàng)建了用于bar 的空全局變量

  • bar被編譯并開(kāi)始執(zhí)行

  • bar導(dǎo)入foo(這是一個(gè)空操作(no-op ),因?yàn)橐呀?jīng)有一個(gè)名為foo的模塊)

  • bar.foo_var = foo.foo_var

最后一步失敗了,因?yàn)镻ython還沒(méi)有解釋foo,而foo的全局符號(hào)字典仍然是空的。

當(dāng)你使用 import foo ,然后嘗試在全局代碼中訪(fǎng)問(wèn) foo.foo_var 時(shí),會(huì)發(fā)生同樣的事情。

這個(gè)問(wèn)題有(至少)三種可能的解決方法。

Guido van Rossum 建議避免使用 from <module> import ... ,并將所有代碼放在函數(shù)中。全局變量和類(lèi)變量的初始化只能使用常量或內(nèi)置函數(shù)。這意味著導(dǎo)入模塊中的所有內(nèi)容都被引用為 <module>.<name>

Jim Roskind建議在每個(gè)模塊中按以下順序執(zhí)行步驟:

  • 導(dǎo)出(全局變量,函數(shù)和不需要導(dǎo)入基類(lèi)的類(lèi))

  • 導(dǎo)入 聲明

  • 活動(dòng)代碼(包括從導(dǎo)入值初始化的全局變量)。

van Rossum不喜歡這種方法,因?yàn)閷?dǎo)入出現(xiàn)在一個(gè)陌生的地方,但這種方法確實(shí)有效。

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āng)我編輯了導(dǎo)入過(guò)的模塊并重新導(dǎo)入它時(shí),這些變化沒(méi)有顯示出來(lái)。為什么會(huì)這樣??

出于效率和一致性的原因,Python僅在第一次導(dǎo)入模塊時(shí)讀取模塊文件。如果不這么做,在一個(gè)由許多模塊組成的程序中,每個(gè)模塊都會(huì)導(dǎo)入相同的基本模塊,那么基本模塊將被解析和重新解析多次。要強(qiáng)制重新讀取已更改的模塊,請(qǐng)執(zhí)行以下操作:

import importlib
import modname
importlib.reload(modname)

警告:這種技術(shù)不是100%萬(wàn)無(wú)一失。特別是包含如下語(yǔ)句的模塊

from modname import some_objects

將繼續(xù)使用舊版本的導(dǎo)入對(duì)象。如果模塊包含類(lèi)定義,則不會(huì)更新現(xiàn)有的類(lèi)實(shí)例以使用新的類(lèi)定義。這可能導(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

如果打印出類(lèi)對(duì)象的“標(biāo)識(shí)”,問(wèn)題的本質(zhì)就會(huì)明確:

>>> hex(id(c.__class__))
'0x7352a0'
>>> hex(id(cls.C))
'0x4198d0'