摘要:隨著大數據與人工智能時代的到來,Python/ target=_blank class=infotextkey>Python 近年來頗受程序員喜愛,在 TIOBE 編程語言排行榜中也穩居第一。但這并不說明 Python 毫無缺點,本文作者就將盤點一些 Python 的“迷惑性為”。
原文鏈接:https://medium.com/geekculture/why-python-still-is-a-mess-1f7bf5bca281
作 者 |Ari Joury
譯者| 彎月
出品 | CSDN(ID:CSDNnews)
長期以來,Python一直自詡是最適合新手程序員的語言之一。話雖沒錯,但這并不意味著編程新手不會對Python的一些行為感到困惑。
舉個例子,動態類型。你無需單獨編寫一行代碼來定義變量的類型,Python能夠自行分辨,乍一看之下,這似乎很神奇。感覺這樣編程速度更快。
然而,就因為少了一行變量定義,整個項目在運行結束之前就有可能崩潰。
說句公道話,許多其他編程語言也使用動態類型。但對于Python而言,這只是一系列噩夢的開始。
隱式的變量聲明會影響閱讀代碼
幾年前,我想在同事編寫的一個軟件的基礎之上,進行二次開發。我知道該軟件的基本思想,我的同事甚至寫了一篇論文作為該軟件的文檔。
但是,我仍然需要閱讀數千行 Python 代碼,才能搞清楚各個部分在干什么,以及我可以將新功能放到哪里。然而,就在這個過程中,我遇到了很大的問題……
縱觀整個代碼庫,變量聲明到處都是。為了搞清楚每個變量的用途,我不得不搜索整個文件,甚至是整個項目。
此外,還有各種各樣的復雜情況,比如函數的某個參數的名字和調用該函數時使用的變量完全不同,或者一個變量與某個類緊密結合,而該類又和另一個類中的某個變量交織在一起……諸如此類的事情層出不窮。
很多人都有類似的感覺,有人就曾表示顯式變量聲明優于隱式(參考鏈接:https://peps.python.org/pep-0020/)。但是,在Python中隱式變量聲明比比皆是,尤其是在大型項目中。
無處不在的可變類型,甚至在函數中
在 Python 中,定義函數的時候可以指定可選參數,即不需要明確指定的參數。如下所示:
defadd_five(a, b= 0) : returna + b + 5通過這個簡單的示例可以看出,在調用函數時,無論指定一個參數還是兩個參數都可以:
add_five(3) # returns 8 add_five(3,4) # returns 12之所以會出現這種現象,是因為表達式b=0定義了b是一個整數,而整數是不可變的。再看看下面這個例子:
def add_element( list=[]): list.Append( "foo") returnlist add_element # returns ["foo"], as expected發現問題了嗎?再執行一次會怎么樣?
- add_element # returns [ "foo", "foo"]! wtf!
因為這里的list已經存在,即["foo"],而Python會繼續向這個列表添加新東西。這是因為列表與整數不同,是可變類型。
我不禁想起一句話:“瘋子就是不斷重復同一件事,卻期待不同的結果。”(經考證,這句話不是愛因斯坦說的)。我想說,Python + 可選參數 + 可變對象 = 瘋子。
類變量并不安全
如果你認為上述問題僅限于可變對象作為可選參數的時候,那你就大錯特錯了。
相信你也使用Python編寫面向對象的代碼,在Python代碼中類無處不在。而類最實用的特性之一便是:繼承。
簡單來說,如果父類具有某些屬性,子類就可以繼承這些屬性。如下所示:
classparent(object): x = 1 classfirstchild(parent): pass classsecondchild(parent): pass print(parent.x, firstchild.x, secondchild.x) # returns 1 1 1注意,這段代碼寫得并不好,不要復制到實際的項目中。關鍵在于,子類繼承了 x = 1,因此我們可以獲取子類的這個屬性,得到的結果與父類相同。
如果我們修改某個子類的x屬性,那么理應說變化的只有這個子類。就好像孩子染發不可能改變父母親或兄弟姐妹的發色。代碼如下:
firstchild.x = 2 print( parent.x, firstchild.x, secondchild.x) # returns 1 2 1如果這時父母染發,孩子的發色會變嗎?不會變,對不對?
parent.x = 3 print( parent.x, firstchild.x, secondchild.x) # returns 3 2 3出現這個結果是因為Python的方法解析順序(http://python-history.blogspot.com/2010/06/method-resolution-order.html)。簡單來說,只要沒有另行說明,子類就會繼承父類擁有的一切。也就是說,在Python的世界里,如果你不提前抗議,那么你媽媽在染頭發的時候,會順帶連你的頭發一起染了。
反方向的作用域
我個人已經因為這個問題多次栽跟頭。
在Python中,函數內部定義的變量無法在函數外部使用,這是因為超出了作用域:
def myfunction(number): basenumber = 2 return basenumber*number basenumber ## Oh no! This is the error: #Traceback (most recent call last): #File "<stdin>", line 1, in<module> #NameError: name 'basenumber'is not defined這部分完全符合直覺,我栽跟頭也不是因為這部分代碼。
但是反過來呢?我的意思是,如果我在函數外部定義一個變量,然后在函數內部引用它呢?
x = 2 def add_5: x = x + 5 print(x) add_5 ## Oh dear... #Traceback (most recent call last): #File "<stdin>", line 1, in<module> #File "<stdin>", line 2, inadd_y #UnboundLocalError: localvariable 'x'referenced before assignment這就很奇怪了,不是嗎?我們生活在一個有樹的世界里,雖然平時我們住在房子里,但肯定也知道樹長什么樣子,對不對?(樹是 x,房子是 add_5,我們是 5……)
有好多次,我在某個類中調用另一個類中定義的函數,就遇到了錯誤。我花了很長一段時間才找到問題的根源。
其背后的基本思想是,函數內部的 x 與外部的 x 是不同的,所以你不能在外部調用它。
幸運的是,這個問題有一個簡單的解決方案,即在 x 之前加一個global,讓x變成全局變量!
x = 2 defadd_5: globalx x = x + 5 print(x) add_5 # works!所以說,如果你認為作用域的目的僅僅是保護函數內部的變量不被外部干擾,那就大錯特錯了。在Python中,局部作用域也無法訪問外部。
在迭代的過程中修改列表
請看如下代碼:
mynumbers = [ x forx inrange( 10)] # thisis[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] forx inrange( len(mynumbers)): ifmynumbers[x]%3 == 0: mynumbers. remove(mynumbers[x]) ## Ew! # Traceback (most recent call last): # File "<stdin>", line2, in <module> # IndexError: list index out of range這個循環出錯,是因為循環在迭代的過程中不斷刪除列表中的元素。因此,列表不斷縮短,循環不可能到達第10個元素,因為它不存在了!
有一種解決方法是,為你想刪除的所有元素統一分配一個值,然后在循環結束后刪除它們。
此外,似乎還有一種更好的解決方式:
mynumbers = [ x forx inrange( 10) ifx%3 ! = 0] # that's what we wanted! [1, 2, 4, 5, 7, 8]只需要一行代碼!
請注意,在上面的示例中,我們使用了 Python 的列表推導式來調用列表。
列表推導式指的是方括號([])中的表達式,一般都是循環的縮寫形式。列表推導式通常比常規循環更快,因此非常適合處理大型數據集。
在這個示例中,我們添加了一個 if 子句來告訴列表推導式:不應包含可被 3 整除的數字。
這個問題與前面的幾個不同,我不認為這是Python的迷惑行為,相反我認為這種處理很聰明,盡管初學者理解起來會有些困難。
總結
實際上,我們對Python的不滿不止是編寫代碼的痛苦,別忘了,以前Python的執行速度非常慢,比大多數其他語言慢 2~10 倍。
現在情況已經好很多了。例如,現在Numpy 包能夠非常快速地處理列表、矩陣等。
Python的多線程處理也變得更加容易了。你可以使用計算機上的多個內核,我曾在 20 個內核上運行進程,為我節省了數周的計算時間。
此外,在過去幾年中,隨著機器學習的蓬勃發展,Python 也表現出了進一步的發展空間。Pytorch 和 Tensorflow 等包的出現推動了Python的采用,而其他語言也正在努力中。
雖然,多年來Python在不斷進步,但這并不能保證Python未來的發展會一帆風順。Python語言的學習并沒有那么簡單,請多加小心。