Python新手在作用域方面經(jīng)常容易碰到的問題
通常,當(dāng)我們定義了一個(gè)全局變量(好吧,我這樣說是因?yàn)橹v解的需要——全局變量是不好的),我們用一個(gè)函數(shù)訪問它們是能被Python理解的:
bar = 42 def foo(): print bar
在這里,我們?cè)趂oo函數(shù)里使用了全局變量bar,然后它也如預(yù)想的能夠正常運(yùn)行:
>>> foo() 42
這樣做很酷。通常,我們?cè)谑褂昧诉@個(gè)特性之后就想在所有的代碼里用上它。如果像以下的例子中使用的話還是能夠正常運(yùn)行的:
bar = [42] def foo(): bar.append(0) foo() >>> print bar [42, 0]
但是,如果我們把bar變一下呢:
>>> bar = 42 ... def foo(): ... bar = 0 ... foo() ... print bar 42
我們可以看到foo函數(shù)運(yùn)行的好好的并且沒有拋出異常,但是當(dāng)我們打印bar的值的時(shí)候會(huì)發(fā)現(xiàn)它的值仍然是42。造成這種情況的原因就是 bar=0 這行代碼,它沒有改變?nèi)肿兞縝ar的值,而是創(chuàng)建了一個(gè)名字也叫bar的局部變量并且它的值為0。這是個(gè)很難發(fā)現(xiàn)的bug,這會(huì)讓沒有真正理解Python作用域的新手非常痛苦。為了理解Python是如何處理局部變量和全局變量的,我們來看一種更少見的,但是可能會(huì)更讓人困惑的錯(cuò)誤,我們?cè)诖蛴ar的值后定義一個(gè)叫bar這個(gè)局部變量:
bar = 42 def foo(): print bar bar = 0
這樣寫應(yīng)該是不會(huì)出錯(cuò)的,不是嗎?我們?cè)诖蛴×酥抵蠖x了相同名稱的變量,所以這應(yīng)該是不會(huì)影響的(Python畢竟是一種解釋型語言),真的是這樣嗎?
出錯(cuò)了
這怎么可能呢?好吧,這里有兩處錯(cuò)誤。第一點(diǎn)就是關(guān)于Python的,作為一種解釋型語言(非常酷,我們都同意這一點(diǎn)),是一行一行地執(zhí)行的。而事實(shí)上,Python是一個(gè)聲明一個(gè)聲明執(zhí)行的。為了讓你對(duì)我想表達(dá)的意思有點(diǎn)感覺,趕緊打開你最愛的shell,然后輸入以下代碼:
def foo():
按回車鍵。正如你看到的,shell里面并沒有打出任何輸出而是等著讓你繼續(xù)函數(shù)的定義。Shell里會(huì)一直這樣直到你停止定義函數(shù)。這是因?yàn)槎x函數(shù)是一個(gè)聲明。好吧,這是一個(gè)混合的聲明,里面包含了一些其他的聲明,但它仍然是一個(gè)聲明。直到函數(shù)被調(diào)用,不然這個(gè)函數(shù)里的內(nèi)容是不會(huì)執(zhí)行的。真正執(zhí)行的是一個(gè)function類型的對(duì)象被創(chuàng)建出來了。
這引導(dǎo)我們來關(guān)注第二點(diǎn)。再?gòu)?qiáng)調(diào)一下,Python的動(dòng)態(tài)性和解釋型的特性讓我們相信當(dāng) print bar 這行被執(zhí)行的時(shí)候,Python會(huì)在首先在局部作用域里尋找叫bar的變量然后再去尋找全局作用域里的。但實(shí)際上發(fā)生的是局部作用域不是完全動(dòng)態(tài)的。當(dāng)def 這個(gè)聲明執(zhí)行的時(shí)候,Python會(huì)靜態(tài)地從這個(gè)函數(shù)的局部作用域里獲取信息。當(dāng)來到 bar=0 這行的時(shí)候(不是執(zhí)行到這行代碼,而是當(dāng)Python解釋器讀到這行代碼的時(shí)候),它會(huì)把'bar'這個(gè)變量加入到foo函數(shù)的局部變量列表里。當(dāng)foo函數(shù)執(zhí)行并且Python準(zhǔn)備執(zhí)行print bar這行的時(shí)候,它就會(huì)在局部的作用域里尋找這個(gè)變量,由于這個(gè)過程是靜態(tài)的,Python知道這個(gè)變量還沒有被賦值,這個(gè)變量沒有值,所以拋出了異常。
你可能會(huì)問:為什么不能在聲明函數(shù)的時(shí)候拋出這個(gè)異常呢?Python可以知道預(yù)先知道bar這個(gè)變量在賦值前被引用了。這個(gè)問題的答案就是Python無法知道這個(gè)局部變量bar是否被賦值了。看看下面的例子:
bar = 42 def foo(baz): if baz > 0: print bar bar = 0
Python在動(dòng)態(tài)和靜態(tài)之間玩了一個(gè)微妙的游戲。它唯一知道的事情就是bar是被賦值了,但它不知道在賦值前被引用這個(gè)異常是否存在直到它真的發(fā)生。好吧,老實(shí)說,它根本就不知道這個(gè)變量是否被賦值!
bar = 42 def foo(): print bar if False: bar = 0 >>> foo() Traceback (most recent call last): File "<pyshell#17>", line 1, in <module> foo() File "<pyshell#16>", line 3, in foo print bar UnboundLocalError: local variable 'bar' referenced before assignment
看到上面的代碼里面,雖然我們作為一種智能生物能夠很清楚的知道不會(huì)給bar賦值。Python無視了那個(gè)事實(shí)而是仍然聲明了bar這個(gè)局部變量。
關(guān)于這個(gè)問題我已經(jīng)說了夠長(zhǎng)了。我們需要的是解決方案,我會(huì)在這里給出兩個(gè)解決方法。
>>> bar = 42 ... def foo(): ... global bar ... print bar ... bar = 0 ... ... foo() 42 >>> bar 0
第一就是使用global關(guān)鍵字。這是不言自明的。這會(huì)讓Python知道bar是一個(gè)全局變量而不是局部變量。
第二個(gè)方法,也是更推薦使用的,就是不要使用全局變量。在我的大量Python開發(fā)工作中從來沒有用到global這個(gè)關(guān)鍵字。能知道怎么用它就行了,但最終還是要盡量避免使用它。如果你想保存在代碼里至始至終用到的值的時(shí)候,把它定義為一個(gè)類的屬性。用這種方法的話就完全不需要用global了,當(dāng)你要用這個(gè)值的時(shí)候,通過類的屬性來訪問就可以了:
>>> class Baz(object): ... bar = 42 ... ... def foo(): ... print Baz.bar # global ... bar = 0 # local ... Baz.bar = 8 # global ... print bar ... ... foo() ... print Baz.bar 42 0 8
- Python運(yùn)行的17個(gè)時(shí)新手常見錯(cuò)誤小結(jié)
- 新手該如何學(xué)python怎么學(xué)好python?
- Python完全新手教程
- Python新手實(shí)現(xiàn)2048小游戲
- python新手經(jīng)常遇到的17個(gè)錯(cuò)誤分析
- 一篇文章入門Python生態(tài)系統(tǒng)(Python新手入門指導(dǎo))
- Python 功能和特點(diǎn)(新手必學(xué))
- 深入理解 Python 中的多線程 新手必看
- 對(duì)Python新手編程過程中如何規(guī)避一些常見問題的建議
- Python新手們?nèi)菀追傅膸讉€(gè)錯(cuò)誤總結(jié)
相關(guān)文章
Python寫的一個(gè)定時(shí)重跑獲取數(shù)據(jù)庫(kù)數(shù)據(jù)
本文給大家分享基于python寫的一個(gè)定時(shí)重跑獲取數(shù)據(jù)庫(kù)數(shù)據(jù)的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2016-12-12Python中xmltodict對(duì)xml的操作方式
這篇文章主要介紹了Python中xmltodict對(duì)xml的操作方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03使用pyQT5顯示網(wǎng)頁的實(shí)現(xiàn)步驟
本文主要介紹了使用pyQT5顯示網(wǎng)頁的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10Python 僅獲取響應(yīng)頭, 不獲取實(shí)體的實(shí)例
今天小編就為大家分享一篇Python 僅獲取響應(yīng)頭, 不獲取實(shí)體的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-08-08Python使用colorlog實(shí)現(xiàn)控制臺(tái)管理日志多種顏色顯示
colorlog 是一個(gè) Python 日志庫(kù),它可以讓你在控制臺(tái)中以彩色的方式顯示日志消息,使得日志更易于閱讀和理解,下面就跟隨小編一起來看看它的具體應(yīng)用吧2024-03-03python opencv設(shè)置攝像頭分辨率以及各個(gè)參數(shù)的方法
下面小編就為大家分享一篇python opencv設(shè)置攝像頭分辨率以及各個(gè)參數(shù)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-04-04Matplotlib.pyplot 三維繪圖的實(shí)現(xiàn)示例
這篇文章主要介紹了Matplotlib.pyplot 三維繪圖的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07python實(shí)現(xiàn)傅里葉級(jí)數(shù)展開的實(shí)現(xiàn)
這篇文章主要介紹了python實(shí)現(xiàn)傅里葉級(jí)數(shù)展開的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-07-07淺談Keras中fit()和fit_generator()的區(qū)別及其參數(shù)的坑
這篇文章主要介紹了Keras中fit()和fit_generator()的區(qū)別及其參數(shù)的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-05-05