當前位置:
首頁 > 知識 > 學慣用 Python 編程時要避免的 3 個錯誤

學慣用 Python 編程時要避免的 3 個錯誤

(點擊

上方藍字

,快速關注我們)




編譯:polebug


linux.cn/article-8780-1.html


如有好文章投稿,請點擊 → 這裡了解詳情





這些錯誤會造成很麻煩的問題,需要數小時才能解決。




當你做錯事時,承認錯誤並不是一件容易的事,但是犯錯是任何學習過程中的一部分,無論是學習走路,還是學習一種新的編程語言都是這樣,比如學習 Python。




為了讓初學 Python 的程序員避免犯同樣的錯誤,以下列出了我學習 Python 時犯的三種錯誤。這些錯誤要麼是我長期以來經常犯的,要麼是造成了需要幾個小時解決的麻煩。




年輕的程序員們可要注意了,這些錯誤是會浪費一下午的!




1、 可變數據類型作為函數定義中的默認參數



這似乎是對的?你寫了一個小函數,比如,搜索當前頁面上的鏈接,並可選將其附加到另一個提供的列表中。





def

search_for_links

(

page

,

add_to

=

[])

:


    

new_links

=

page

.

search_for_links

()


    

add_to

.

extend

(

new_links

)


    

return

add_to




從表面看,這像是十分正常的 Python 代碼,事實上它也是,而且是可以運行的。但是,這裡有個問題。如果我們給 add_to 參數提供了一個列表,它將按照我們預期的那樣工作。但是,如果我們讓它使用默認值,就會出現一些神奇的事情。




試試下面的代碼:





def

fn

(

var1

,

var2

=

[])

:


    

var2

.

append

(

var1

)


    

print

var2


fn

(

3

)


fn

(

4

)


fn

(

5

)




可能你認為我們將看到:





[

3

]


[

4

]


[

5

]




但實際上,我們看到的卻是:





[

3

]


[

3

,

4

]


[

3

,

4

,

5

]




為什麼呢?如你所見,每次都使用的是同一個列表,輸出為什麼會是這樣?在 Python 中,當我們編寫這樣的函數時,這個列表被實例化為函數定義的一部分。當函數運行時,它並不是每次都被實例化。這意味著,這個函數會一直使用完全一樣的列表對象,除非我們提供一個新的對象:





fn(3, [4])





[4, 3]




答案正如我們所想的那樣。要想得到這種結果,正確的方法是:





def

fn

(

var1

,

var2

=

None

)

:


    

if

not

var2

:


        

var2

=

[]


    

var2

.

append

(

var1

)




或是在第一個例子中:





def

search_for_links

(

page

,

add_to

=

None

)

:


    

if

not

add_to

:


        

add_to

=

[]


    

new_links

=

page

.

search_for_links

()


    

add_to

.

extend

(

new_links

)


    

return

add_to




這將在模塊載入的時候移走實例化的內容,以便每次運行函數時都會發生列表實例化。請注意,對於不可變數據類型,比如元組、字元串、整型,是不需要考慮這種情況的。這意味著,像下面這樣的代碼是非常可行的:





def

func

(

message

=

"my message"

)

:


    

print

message




2、 可變數據類型作為類變數




這和上面提到的最後一個錯誤很相像。思考以下代碼:





class

URLCatcher

(

object

)

:


    

urls

=

[]


    

def

add_url

(

self

,

url

)

:


        

self

.

urls

.

append

(

url

)




這段代碼看起來非常正常。我們有一個儲存 URL 的對象。當我們調用 add_url 方法時,它會添加一個給定的 URL 到存儲中。看起來非常正確吧?讓我們看看實際是怎樣的:





a

=

URLCatcher

()


a

.

add_url

(

"http://www.google.com"

)


b

=

URLCatcher

()


b

.

add_url

(

"http://www.bbc.co.hk"

)




b.urls:





["http://www.google.com", "http://www.bbc.co.uk"]




a.urls:





["http://www.google.com", "http://www.bbc.co.uk"]




等等,怎麼回事?!我們想的不是這樣啊。我們實例化了兩個單獨的對象 a 和 b。把一個 URL 給了 a,另一個給了 b。這兩個對象怎麼會都有這兩個 URL 呢?




這和第一個錯例是同樣的問題。創建類定義時,URL 列表將被實例化。該類所有的實例使用相同的列表。在有些時候這種情況是有用的,但大多數時候你並不想這樣做。你希望每個對象有一個單獨的儲存。為此,我們修改代碼為:





class

URLCatcher

(

object

)

:


    

def

__init__

(

self

)

:


        

self

.

urls

=

[]


    

def

add_url

(

self

,

url

)

:


        

self

.

urls

.

append

(

url

)




現在,當創建對象時,URL 列表被實例化。當我們實例化兩個單獨的對象時,它們將分別使用兩個單獨的列表。




3、 可變的分配錯誤




這個問題困擾了我一段時間。讓我們做出一些改變,並使用另一種可變數據類型 – 字典。





a = {"1": "one", "2": "two"}




現在,假設我們想把這個字典用在別的地方,且保持它的初始數據完整。





b

=

a


 


b

[

"3"

]

=

"three"




簡單吧?




現在,讓我們看看原來那個我們不想改變的字典 a:





{"1": "one", "2": "two", "3": "three"}




哇等一下,我們再看看 b?





{"1": "one", "2": "two", "3": "three"}




等等,什麼?有點亂……讓我們回想一下,看看其它不可變類型在這種情況下會發生什麼,例如一個元組:





c

=

(

2

,

3

)


d

=

c


d

=

(

4

,

5

)




現在 c 是 (2, 3),而 d 是 (4, 5)。




這個函數結果如我們所料。那麼,在之前的例子中到底發生了什麼?當使用可變類型時,其行為有點像 C 語言的一個指針。在上面的代碼中,我們令 b = a,我們真正表達的意思是:b 成為 a 的一個引用。它們都指向 Python 內存中的同一個對象。聽起來有些熟悉?那是因為這個問題與先前的相似。其實,這篇文章應該被稱為「可變引發的麻煩」。




列表也會發生同樣的事嗎?是的。那麼我們如何解決呢?這必須非常小心。如果我們真的需要複製一個列表進行處理,我們可以這樣做:





b = a[:]




這將遍歷並複製列表中的每個對象的引用,並且把它放在一個新的列表中。但是要注意:如果列表中的每個對象都是可變的,我們將再次獲得它們的引用,而不是完整的副本。




假設在一張紙上列清單。在原來的例子中相當於,A 某和 B 某正在看著同一張紙。如果有個人修改了這個清單,兩個人都將看到相同的變化。當我們複製引用時,每個人現在有了他們自己的清單。但是,我們假設這個清單包括尋找食物的地方。如果「冰箱」是列表中的第一個,即使它被複制,兩個列表中的條目也都指向同一個冰箱。所以,如果冰箱被 A 修改,吃掉了裡面的大蛋糕,B 也將看到這個蛋糕的消失。這裡沒有簡單的方法解決它。只要你記住它,並編寫代碼的時候,使用不會造成這個問題的方式。




字典以相同的方式工作,並且你可以通過以下方式創建一個昂貴副本:





b = a.copy()




再次說明,這隻會創建一個新的字典,指向原來存在的相同的條目。因此,如果我們有兩個相同的列表,並且我們修改字典 a 的一個鍵指向的可變對象,那麼在字典 b 中也將看到這些變化。




可變數據類型的麻煩也是它們強大的地方。以上都不是實際中的問題;它們是一些要注意防止出現的問題。在第三個項目中使用昂貴複製操作作為解決方案在 99% 的時候是沒有必要的。你的程序或許應該被改改,所以在第一個例子中,這些副本甚至是不需要的。




編程快樂!在評論中可以隨時提問。






作者簡介:




Pete Savage – Peter 是一位充滿激情的開源愛好者,在過去十年里一直在推廣和使用開源產品。他從 Ubuntu 社區開始,在許多不同的領域自願參與音頻製作領域的研究工作。在職業經歷方面,他起初作為公司的系統管理員,大部分時間在管理和建立數據中心,之後在 Red Hat 擔任 CloudForms 產品的主要測試工程師。




看完本文有收穫?請轉

發分享給更多人


關注「P

ython開發者」,提升Python技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python開發者 的精彩文章:

如何開發一個 PyCharm 插件
Python 魔術方法(Magic Method)
隨機之美,隨機森林
Django 使用 Celery 實現非同步任務
用 Python 做股市數據分析(二)

TAG:Python開發者 |

您可能感興趣

住宿就避免不了的 Roommate Type
Reddit: 吹牛時應該避免的錯誤姿勢
避免使用AtomArrayBuffers中的競爭條件
怎樣的交易會被計入eBay defect,如何避免這些defect呢?
Kubernetes監控方面要避免的四個常見陷阱
買iPhone之前一定要查詢序列號,避免買到問題iPhone
製作人談堡壘之夜Fortnite是如何避免Pay-To-Win
跑步怎樣避免winter slide?
如何用CSRF tokens避免CSRF攻擊
怎樣避免起一個Angelababy式的名字
避免陷入 async/await 地獄
避免這些,你的婚禮Prefect!
iPhone X 上的這 6 項功能要慎用,避免隱私泄露
Waymo:我們的技術可以避免Uber那樣的致命事故
Facebook變Fakebook?小扎5小時鏖戰,本可避免!
蘋果:iPhone8和X不會降速 但這個問題無法避免
如何避免買到假iPhone?如何購買正版iPhone
XJar: Spring-Boot JAR 包加密運行工具,避免源碼泄露以及反編譯
怎樣避免起一個Angelababy式的中文名
InvisibleShield新型屏幕保護膜有助於避免藍光