你必定遇到過Python中的無效語法:SyntaxError---常見緣由以及解決辦法



Python以其簡單的語法而聞名。然而,當您第一次學習Python時,或者當您具備另外一種編程語言的堅實背景時,您可能會遇到一些Python不容許的事情。若是您在嘗試運行Python代碼時收到過SyntaxError錯誤,那麼本指南能夠幫助您。在本教程中,您將看到Python中常見的無效語法示例,並學習如何解決這個問題。python


在本教程結束時,您將可以:web

  • 識別Python中的無效語法shell

  • 理解SyntaxError回溯編程

  • 解析無效語法或徹底阻止它微信




Python中的無效語法


當您運行Python代碼時,解釋器將首先解析它,將其轉換成Python字節碼,而後執行。解釋器將在程序執行的第一階段(也稱爲解析階段)中發現Python中的任何無效語法。若是解釋器不能成功地解析您的Python代碼,那麼這意味着您在代碼的某個地方使用了無效的語法。解釋器將嘗試向您顯示錯誤發生的位置。app


當您第一次學習Python時,獲得一個SyntaxError可能會使人沮喪。Python將嘗試幫助您肯定無效語法在代碼中的位置,可是它提供的回溯可能會讓您感到有些困惑。有時,它所指向的代碼是徹底正確的。機器學習


您不能像處理其餘異常同樣處理Python中的無效語法。即便您嘗試將try和except塊封裝到帶有無效語法的代碼中,您仍然會看到解釋器拋出一個SyntaxError。編程語言


SyntaxError異常和回溯


當解釋器在Python代碼中遇到無效語法時,它將拋出一個SyntaxError異常,並提供一個帶有一些有用信息的回溯,以幫助您調試錯誤。下面是一些Python中包含無效語法的代碼:編輯器


 1 # theofficefacts.py
2 ages = {
3 'pam': 24,
4 'jim': 24
5 'michael': 43
6 }
7 print(f'Michael is {ages["michael"]} years old.')


您能夠在第4行字典的文字中看到無效的語法。第二個詞條「jim」漏掉了一個逗號。若是你試着按原樣運行這段代碼,你會獲得如下回溯結果:
ide


$ python theofficefacts.py
File "theofficefacts.py", line 5
'michael': 43
^
SyntaxError: invalid syntax


注意,traceback消息定位的錯誤在第5行,而不是第4行。Python解釋器試圖指出無效語法的位置。然而,它只能指出它最初注意到的問題。當您得到一個SyntaxError traceback,而且traceback所指向的代碼看起來很好,那麼您將但願開始向後移動代碼,直到您可以肯定哪裏出了問題。


在上面的例子中,根據後面的內容,省略逗號是沒有問題的。例如,第5行「michael」後面缺乏逗號是沒有問題的。可是一旦解釋器遇到不理解的東西,它只能指出它發現的第一件不理解的事情。


回溯是一個堆棧跟蹤,從異常處理程序的點一直到調用鏈中的異常引起點。您還能夠從調用的角度(而且沒有錯誤的上下文)向上使用當前調用堆棧,這對於查找函數所遵循的路徑很是有用。


有幾個元素的SyntaxError回溯,能夠幫助您肯定無效的語法在您的代碼:


  • 遇到無效語法的文件名

  • 遇到問題的行號和代碼的複寫行

  • 在複製代碼下面的行中有一個插入符號(^),它向您顯示代碼中有問題的那一點

  • 異常類型SyntaxError以後的錯誤消息,能夠提供幫助您肯定問題的信息


在上面的例子中,給出的文件名是theofficefacts。行號爲5,插入符號指向字典鍵michael的結束引用。SyntaxError回溯可能不會指向真正的問題,但它將指向解釋器沒法理解語法的第一個地方。


您可能會看到Python引起另外兩個異常。它們等價於SyntaxError,但有不一樣的名稱:


  1. IndentationError

  2. TabError


這些異常都繼承自SyntaxError類,但它們是涉及縮進的特殊狀況。當代碼的縮進級別不匹配時,將引起IndentationError。當代碼在同一文件中同時使用製表符和空格時,將引起一個製表符錯誤。在後面的小節中,您將進一步瞭解這些異常。


常見的語法問題


當您第一次遇到SyntaxError時,瞭解爲何會出現問題以及如何修復Python代碼中的無效語法是頗有幫助的。在下面的小節中,您將看到可能引起SyntaxError的一些更常見的緣由,以及如何修復它們。


0 1

誤用賦值運算符(=)


在Python中有幾種狀況下,您不能對對象進行賦值。一些例子是分配文字和函數調用。在下面的代碼塊中,您能夠看到一些嘗試這樣作的示例和由此產生的SyntaxError回溯:


>>>

>>> len('hello') = 5
File "<stdin>", line 1
SyntaxError: can't assign to function call

>>> 'foo' = 1
File "<stdin>", line 1
SyntaxError: can't assign to literal

>>> 1 = 'foo'
File "<stdin>", line 1
SyntaxError: can't assign to literal

第一個示例嘗試將值5分配給len()調用。在這種狀況下,SyntaxError消息很是有用。它告訴你不能給函數調用賦值。


第二個和第三個示例嘗試將字符串和整數分配給文字。一樣的規則也適用於其餘文字值。一樣,回溯消息代表,當您試圖將一個值賦給一個文字時,問題就會發生。


注意:上面的示例缺乏重複的代碼行和指向回溯中的問題的插入符號(^)。當您在REPL中嘗試從文件中執行這段代碼時,您看到的異常和回溯將是不一樣的。若是這個代碼在一個文件中,那麼您將獲得重複的代碼行和指向問題的插入符號,正如您在本教程的其餘狀況中看到的那樣。


極可能你的目的不是給文字或函數調用賦值。例如,若是您不當心省略了額外的等號(=),就會發生這種狀況,這會將賦值轉換爲比較。以下所示,比較是有效的:


>>>

>>> len('hello') == 5
True

大多數狀況下,當Python告訴您正在對沒法賦值的東西進行賦值時,您首先可能須要檢查以確保語句不該該是布爾表達式。當您試圖爲Python關鍵字賦值時,也可能遇到這個問題,下一節將討論這個問題。


0 2
拼寫錯誤、缺乏或誤用Python關鍵字


Python關鍵字是一組在Python中具備特殊含義的受保護的單詞。這些詞在代碼中不能用做標識符、變量或函數名。它們是語言的一部分,只能在Python容許的上下文中使用。


有三種常見的方式,你能夠錯誤地使用關鍵字:

  1. 拼錯的關鍵字

  2. 缺乏一個關鍵字

  3. 濫用關鍵字


若是您在Python代碼中拼錯了關鍵字,那麼您將獲得一個SyntaxError。例如,若是你把關鍵字拼錯了,會發生如下狀況:


>>> fro i in range(10):
File "<stdin>", line 1
fro i in range(10):
^
SyntaxError: invalid syntax

消息將讀取SyntaxError:無效語法,但這沒有多大幫助。回溯指向Python能夠檢測到錯誤的第一個地方。要修復這類錯誤,請確保全部Python關鍵字拼寫正確。


另外一個關於關鍵字的常見問題是你徹底忽略了它們:


>>>

>>> for i range(10):
File "<stdin>", line 1
for i range(10):
^
SyntaxError: invalid syntax

一樣,異常消息也不是頗有用,可是回溯確實試圖爲您指出正確的方向。若是從插入符號返回,則能夠看到for循環語法中缺乏關鍵字in


您還可能誤用受保護的Python關鍵字。記住,關鍵字只容許在特定的狀況下使用。若是您不正確地使用它們,那麼您的Python代碼中就會出現無效的語法。一個常見的例子是在循環外使用continue或break。這在開發過程當中很容易發生,當你在實現一些東西的時候,碰巧把邏輯移出了一個循環:


>>> names = ['pam', 'jim', 'michael']
>>> if 'jim' in names:
... print('jim found')
... break
...
File "<stdin>", line 3
SyntaxError: 'break' outside loop

>>> if 'jim' in names:
... print('jim found')
... continue
...
File "<stdin>", line 3
SyntaxError: 'continue' not properly in loop


在這裏,Python很好地告訴了您到底哪裏出了問題。"'break' outside loop"和" continue' not exactly in loop"這兩個信息能夠幫助你明確地知道該怎麼作。若是這段代碼在一個文件中,那麼Python也會讓插入符號指向被誤用的關鍵字。


另外一個例子是,若是你嘗試給一個變量分配一個Python關鍵字,或者使用一個關鍵字來定義一個函數:


>>>

>>> pass = True
File "<stdin>", line 1
pass = True
^
SyntaxError: invalid syntax
>>> def pass():
File "<stdin>", line 1
def pass():
^
SyntaxError: invalid syntax

當您試圖爲pass分配一個值時,或者當您試圖定義一個名爲pass的新函數時,您將獲得一個SyntaxError並再次看到「無效語法」消息。


在Python代碼中解決這種類型的無效語法可能會稍微困難一些,由於代碼從外部看起來沒什麼問題。若是您的代碼看起來不錯,可是您仍然會獲得一個SyntaxError,那麼您能夠考慮檢查您想要使用的變量名或函數名與您正在使用的Python版本的關鍵字列表。


受保護的關鍵字列表隨着Python的每一個新版本而改變。例如,在Python 3.6中,您可使用await做爲變量名或函數名,可是在Python 3.7中,這個單詞已經被添加到關鍵字列表中。如今,若是您嘗試使用await做爲變量名或函數名,若是您的代碼是Python 3.7或更高版本,那麼這將致使SyntaxError。


另外一個例子是print,它在python2和python3中有所不一樣:


Version print Type Takes A Value
Python 2 keyword no
Python 3 built-in function yes


print是python2中的一個關鍵字,因此你不能給它賦值。然而,在python3中,它是一個能夠賦值的內置函數。


你能夠運行如下代碼來查看關鍵字列表,不管你運行的Python版本是什麼:


import keyword
print(keyword.kwlist)yword.iskey

keyword還提供了有用的keyword.iskeyword()。若是你只是須要一個快速的方法來檢查經過變量,那麼你可使用如下一行:

>>> import keyword; keyword.iskeyword('pass')
True

這段代碼將快速告訴您要使用的標識符是不是關鍵字。


03

缺乏括號、方括號和引號

一般,Python代碼中無效語法的緣由是缺乏或不匹配的右括號、方括號或引號。在嵌套圓括號的很長行或更長的多行塊中很難發現這些。你能夠經過Python的回溯來發現不匹配或缺失的引用:


>>>

>>> message = 'don't'
File "<stdin>", line 1
message = 'don't'
^
SyntaxError: invalid syntax


這裏,回溯指向無效代碼,其中在結束單引號後有一個t'。要解決這個問題,您能夠進行如下兩種更改之一:


  1. 用反斜槓轉義單引號('don\t')

  2. 將整個字符串用雙引號括起來(「don't」)


另外一個常見的錯誤是忘記關閉字符串。對於雙引號和單引號字符串,狀況和回溯是相同的:


>>>

>>> message = "This is an unclosed string
File "<stdin>", line 1
message = "This is an unclosed string
^
SyntaxError: EOL while scanning string literal

這一次,traceback中的插入符號指向問題代碼。SyntaxError消息「在掃描字符串文字時的EOL」更具體一些,有助於肯定問題。這意味着Python解釋器在一個開放字符串關閉以前到達該行(EOL)的末尾。要解決這個問題,請使用與開始時匹配的引號關閉字符串。在本例中,將使用雙引號(")。

在f-string語句中缺乏引號也會致使Python中無效的語法:


 1 # theofficefacts.py
2 ages = {
3 'pam': 24,
4 'jim': 24,
5 'michael': 43
6 }
7 print(f'Michael is {ages["michael]} years old.')

這裏,打印的f-string中對ages字典的引用缺乏關鍵引用的雙引號。獲得的回溯結果以下:


$ python theofficefacts.py
File "theofficefacts.py", line 7
print(f'Michael is {ages["michael]} years old.')
^
SyntaxError: f-string: unterminated string

Python識別問題並告訴您它存在於f-string中。消息「未終止字符串」也指出了問題所在。本例中的插入符號僅指向f-string的開頭。


當插入符號指向f-string的問題區域時,這可能沒有那麼有用,可是它確實縮小了您須要查找的範圍。在那個f字串的某個地方有一個未終止的字符串。你只須要找到在哪裏。要修復此問題,請確保全部內部f-string引號和方括號都已存在。


缺乏括號和方括號的狀況大體相同。例如,若是您從列表中刪除了右方括號,那麼Python將會發現並指出它。然而,這有一些變化。第一種是把列表中的右括號去掉:


# missing.py
def foo():
return [1, 2, 3

print(foo())

當你運行這段代碼時,你會被告知調用print()有一個問題:


$ python missing.py
File "missing.py", line 5
print(foo())
^
SyntaxError: invalid syntax

這裏發生的是Python認爲列表包含三個元素:一、2和3 print(foo())。Python使用空格從邏輯上對事物進行分組,由於從print(foo())中沒有逗號或括號分隔3,因此Python將它們集中在一塊兒做爲列表的第三個元素。


另外一種變化是在列表的最後一個元素後面添加一個逗號,同時仍然去掉右方括號:


# missing.py
def foo():
return [1, 2, 3,

print(foo())

如今你獲得了一個不一樣的回溯:


$ python missing.py
File "missing.py", line 6

^
SyntaxError: unexpected EOF while parsing

在前面的例子中,3和print(foo())被集中在一塊兒做爲一個元素,可是在這裏你能夠看到一個逗號將二者分開。如今,print(foo())的調用被添加爲列表的第四個元素,Python到達了文件的末尾,但沒有使用右括號。回溯告訴您,Python已經到達了文件(EOF)的末尾,可是它還在期待其餘內容。

在本例中,Python但願有一個右括號(]),可是重複的行和插入符號沒有多大幫助。缺乏括號和方括號是Python很難識別的。有時,您惟一能作的就是從插入符號開始,而後向後移動,直到您可以識別出缺失或錯誤的地方。


04
字典語法錯誤


您在前面已經看到,若是將dictionary元素中的逗號去掉,可能會獲得SyntaxError。Python字典的另外一種無效語法形式是使用等號(=)來分隔鍵和值,而不是冒號:


>>>

>>> ages = {'pam'=24}
File "<stdin>", line 1
ages = {'pam'=24}
^
SyntaxError: invalid syntax

一樣,這個錯誤消息不是頗有用。可是,重複的行和插入符號很是有用!他們直接指向問題人物。


若是您將Python語法與其餘編程語言的語法混淆,那麼這種類型的問題很常見。若是將定義字典的行爲與dict()調用相混淆,也會看到這種狀況。要解決這個問題,能夠用冒號替換等號。你也能夠切換到使用dict():


>>>

>>> ages = dict(pam=24)
>>> ages
{'pam': 24}

若是dict()語法更有幫助,那麼可使用它來定義字典。


05

使用錯誤的縮進

SyntaxError有兩個子類專門處理縮進問題:

  1. IndentationError

  2. TabError


其餘編程語言使用花括號表示代碼塊,而Python使用空格。這意味着Python指望代碼中的空白具備可預測性。若是代碼塊中有一行空格數錯誤,則會引起IndentationError:


 1 # indentation.py
2 def foo():
3 for i in range(10):
4 print(i)
5 print('done')
6
7 foo()

這可能很難看到,可是第5行只縮進了2個空格。它應該與for循環語句一致,也就是4個空格。幸運的是,Python能夠很容易地發現這一點,並很快告訴您問題所在。

不過這裏也有一點模糊。打印('done')行是打算在for循環以後仍是在for循環塊內部?當你運行上述代碼,你會看到如下錯誤:


$ python indentation.py
File "indentation.py", line 5
print('done')
^
IndentationError: unindent does not match any outer indentation level

雖然回溯看起來很像SyntaxError回溯,但它其實是一個IndentationError。錯誤消息也很是有用。它告訴您行的縮進級別與其餘任何縮進級別不匹配。換句話說,print('done')是縮進2個空格的,可是Python找不到任何其餘匹配這種縮進級別的代碼行。您能夠經過確保代碼行符合預期的縮進級別來快速修復此問題。


SyntaxError的另外一種類型是TabError,只要有一行包含製表符或空格做爲縮進,而文件的其他部分包含製表符或空格,就會看到TabError。這可能會隱藏起來,直到Python指出來!


若是製表符的大小與每一個縮進級別中的空格數相同,那麼看起來全部的行彷佛都在同一級別上。然而,若是一行用空格縮進,另外一行用製表符縮進,那麼Python會指出這是一個問題:


 1 # indentation.py
2 def foo():
3 for i in range(10):
4 print(i)
5 print('done')
6
7 foo()

在這裏,第5行是用製表符縮進的,而不是用4個空格。根據您的系統設置,這個代碼塊在您看來多是完美的,也多是徹底錯誤的。

可是,Python會當即注意到這個問題。但在你運行代碼,看看Python會告訴你什麼是錯誤的以前,它可能會對你有幫助,看看在不一樣的標籤寬度設置下的代碼是什麼樣子的一個例子:


$ tabs 4 # Sets the shell tab width to 4 spaces
$ cat -n indentation.py
1 # indentation.py
2 def foo():
3 for i in range(10)
4 print(i)
5 print('done')
6
7 foo()

$ tabs 8 # Sets the shell tab width to 8 spaces (standard)
$ cat -n indentation.py
1 # indentation.py
2 def foo():
3 for i in range(10)
4 print(i)
5 print('done')
6
7 foo()

$ tabs 3 # Sets the shell tab width to 3 spaces
$ cat -n indentation.py
1 # indentation.py
2 def foo():
3 for i in range(10)
4 print(i)
5 print('done')
6
7 foo()


請注意上面三個示例之間的顯示差別。大多數代碼爲每一個縮進級別使用4個空格,可是第5行在全部3個示例中都使用單個選項卡。標籤寬度的變化,基於標籤寬度的設置:


  • 若是製表符寬度爲4,那麼print語句看起來就像是在for循環以外。控制檯將在循環結束時打印「done」。

  • 若是製表符寬度是8,這是不少系統的標準,那麼print語句看起來就像是在for循環中。控制檯會在每一個數字以後打印「done」。

  • 若是製表符寬度爲3,那麼print語句看起來就不合適。在本例中,第5行與任何縮進級別不匹配。


當你運行代碼,你會獲得如下錯誤和回溯:


$ python indentation.py
File "indentation.py", line 5
print('done')
^
TabError: inconsistent use of tabs and spaces in indentation

請注意TabError而不是一般的SyntaxError。Python指出問題所在,並給出有用的錯誤消息。它清楚地告訴您,在同一個文件中,製表符和空格用於縮進。

解決這個問題的方法是讓同一Python代碼文件中的全部行都使用製表符或空格,而不是同時使用製表符和空格。對於上面的代碼塊,修復方法是刪除選項卡並用4個空格替換它,這將在For循環完成後打印「done」。

06
定義和調用函數



在定義或調用函數時,可能會遇到Python中無效的語法。例如,若是在函數定義的末尾使用分號而不是冒號,就會看到SyntaxError:


>>>

>>> def fun();
File "<stdin>", line 1
def fun();
^
SyntaxError: invalid syntax

這裏的回溯很是有用,插入符號直接指向問題字符。您能夠經過將分號替換爲冒號來清除Python中的這種無效語法。

此外,函數定義和函數調用中的關鍵字參數的順序必須正確。關鍵字參數老是跟在位置參數以後。若是不使用此順序,將致使SyntaxError:


>>>

>>> def fun(a, b):
... print(a, b)
...
>>> fun(a=1, 2)
File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

這裏,一樣,錯誤消息很是有助於準確地告訴您這行代碼的錯誤之處。


07

Python版本問題

有時候,在一個Python版本中工做得很好的代碼會在一個新版本中中斷。這是因爲語言語法的官方變化。這方面最著名的例子是print語句,它從python2中的一個關鍵字變成了python3中的一個內置函數:


>>>

>>> # Valid Python 2 syntax that fails in Python 3
>>> print 'hello'
File "<stdin>", line 1
print 'hello'
^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print('hello')?

這是SyntaxError提供的錯誤消息所展現的示例之一!它不只告訴您在print調用中缺乏圓括號,並且還提供了正確的代碼來幫助您修復語句。


您可能會遇到的另外一個問題是,當您閱讀或學習在較新版本的Python中有效的語法,但在您正在編寫的版本中無效的語法時。f-string語法就是一個例子,在3.6以前的Python版本中是不存在的:


>>>

>>> # Any version of python before 3.6 including 2.7
>>> w ='world'
>>> print(f'hello, {w}')
File "<stdin>", line 1
print(f'hello, {w}')
^
SyntaxError: invalid syntax

在3.6以前的Python版本中,解釋器對f-string語法一無所知,只會提供一個通用的「無效語法」消息。在本例中,問題是代碼看起來很是好,可是它是在較老版本的Python中運行的。若是有疑問,請再次檢查正在運行的Python版本!


Python語法在不斷髮展,Python 3.8中引入了一些很酷的新特性:


  1. Walrus操做符(賦值表達式)

  2. 用於調試的F-string語法

  3. Positional-only參數


若是您想嘗試這些新特性,那麼您須要確保您是在Python 3.8環境中工做。不然,您將獲得一個SyntaxError。


Python 3.8還提供了新的SyntaxWarning。在語法有效但看起來仍然可疑的狀況下,您將看到此警告。例如,若是列表中的兩個元組之間缺乏逗號。這在3.8以前的Python版本中是有效的語法,可是代碼會產生一個類型錯誤,由於tuple是不可調用的:


>>>

>>> [(1,2)(2,3)]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object is not callable


這個類型錯誤意味着您不能像調用函數那樣調用元組,這是Python解釋器認爲您正在作的。


在Python 3.8中,這段代碼仍然會引起類型錯誤,可是如今您還會看到一個SyntaxWarning,它指示如何着手修復問題:


>>>

>>> [(1,2)(2,3)]
<stdin>:1: SyntaxWarning: 'tuple' object is not callable; perhaps you missed a comma?
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object is not callable

新SyntaxWarning附帶的有用信息甚至提供了一個提示(「也許您漏掉了一個逗號?」),以便爲您指出正確的方向!


結論


在本教程中,您已經看到了SyntaxError回溯所提供的信息。您還看到了Python中許多常見的無效語法示例,以及這些問題的解決方案。這不只會加快你的工做流程,並且還會使你成爲一個更有幫助的代碼審查者!


在編寫代碼時,請嘗試使用可以理解Python語法並提供反饋的IDE。若是您將本教程中的許多無效Python代碼示例放到一個良好的IDE中,那麼它們應該在您執行代碼以前突出顯示問題行。


在學習Python時得到一個SyntaxError可能會使人沮喪,可是如今您知道了如何理解回溯消息以及在Python中可能遇到的無效語法形式。下一次出現SyntaxError時,您就能夠更好地快速修復這個問題了!


原文請點擊左下角閱讀原文


·END·
 

掃碼關注

機器學習·數據分析


本文分享自微信公衆號 - Python學會(gh_39aead19f756)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索