Python+eval函數(shù)實現(xiàn)動態(tài)地計算數(shù)學(xué)表達式詳解
Python的 eval() 允許從基于字符串或基于編譯代碼的輸入中計算任意Python表達式。當從字符串或編譯后的代碼對象的任何輸入中動態(tài)計算Python表達式時,此函數(shù)非常方便。
本文中,云朵君將和大家一起從如下兩個方面展開學(xué)習(xí)。
- Python的 eval() 如何工作
- 如何使用 eval() 來動態(tài)地計算任意基于字符串或基于編譯代碼的輸入
此外,后期推文將一起學(xué)習(xí)如何使用 Python 的 eval() 來編碼一個交互式地計算數(shù)學(xué)表達式的應(yīng)用程序。通過這個例子,我們將把所學(xué)到的關(guān)于 eval() 的一切應(yīng)用于一個實際問題。
Python 的 eval()
我們可以使用內(nèi)置的 Python eval() 從基于字符串或基于編譯代碼的輸入中動態(tài)地計算表達式。如果我們向 eval() 傳遞一個字符串,那么該函數(shù)會解析它,將其編譯為字節(jié)碼,并將其作為一個 Python 表達式進行計算。但是如果我們用一個編譯過的代碼對象調(diào)用 eval(),那么該函數(shù)只執(zhí)行計算步驟,如果我們用相同的輸入多次調(diào)用 eval(),這就非常方便了。
Python的 eval() 的定義如下。
eval(expression[,?globals[,?locals]])
該函數(shù)需要一個第一個參數(shù),稱為expression,它包含了需要計算的表達式。eval()還需要兩個可選參數(shù)。
- globals
- locals
在接下來的內(nèi)容中,我們將學(xué)習(xí)這些參數(shù)是什么,以及 eval() 如何使用它們來即時計算Python 表達式。
注意: 我們也可以使用 exec()來動態(tài)地執(zhí)行 Python 代碼。eval() 和 exec() 的主要區(qū)別是,eval() 只能執(zhí)行或計算表達式,而 exec() 可以執(zhí)行任何一段 Python代碼。
第一個參數(shù):expression
eval() 的第一個參數(shù)稱為 expression,它是一個必需的參數(shù),用于保存函數(shù)的 基于字符串 或 基于編譯碼的 輸入。當調(diào)用 eval() 時,expression 的內(nèi)容被作為 Python 表達式進行計算。下面是使用基于字符串的輸入的例子。
>>>?eval("2?**?8")
256
>>>?eval("1024?+?1024")
2048
>>>?eval("sum([8,?16,?32])")
56
>>>?x?=?100
>>>?eval("x?*?2")
200
當用一個字符串作為參數(shù)調(diào)用 eval() 時,該函數(shù)返回對輸入字符串進行計算的結(jié)果。默認情況下,eval()可以訪問全局變量名,如上例中的x。
為了計算一個基于字符串的表達式,Python 的 eval() 運行以下步驟。
- 解析表達式
- 將其編譯為字節(jié)碼
- 將其作為一個Python表達式進行計算
- 返回計算的結(jié)果
eval()的第一個參數(shù) expression 強調(diào)了該函數(shù)只作用于表達式,并非復(fù)合語句。Python 文檔對 expression 的定義如下。
expression
一段可以被計算為某種值的語法。換句話說,表達式是表達式元素的累積,如字面意義、名稱、屬性訪問、運算符或函數(shù)調(diào)用,它們都返回一個值。與許多其他語言相比,并非所有的語言結(jié)構(gòu)都是表達式。也有一些語句不能作為表達式使用,如 while。此外賦值也是語句,不是表達式。
另一方面,Python statement 有如下定義。
statement
statement是一個套件(一個代碼 "塊")的一部分。statement要么是一個表達式,要么是帶有關(guān)鍵字的幾個結(jié)構(gòu)體之一,如 if、while或for。
如果向eval()傳遞一個復(fù)合語句,那么會得到一個 SyntaxError。下面的例子是用eval()來執(zhí)行一個if語句。
>>>?x?=?100
>>>?eval("if?x:?print(x)")
??File?"<string>",?line?1
????if?x:?print(x)
????^
SyntaxError:?invalid?syntax
上面報錯是因為 eval() 只接受表達式。任何其它語句,如 if、for、while、import、def 或 class,都會引發(fā)錯誤。
注意: for 循環(huán)是一個復(fù)合語句,但是 for 關(guān)鍵字也可以用在推導(dǎo)式中,此時它被認為是表達式。可以使用eval() 來計算推導(dǎo)式,即使它們使用了 for 關(guān)鍵字。
eval()也不允許進行賦值操作。
>>>?eval("pi?=?3.1416")
??File?"<string>",?line?1
????pi?=?3.1416
???????^
SyntaxError:?invalid?syntax
如果我們將一個賦值操作作為參數(shù)傳遞給 eval() ,那么會得到一個 SyntaxError。賦值操作是語句,而不是表達式,語句不允許與 eval() 一起使用。
當解析器不理解輸入的表達式時,也會得到一個 SyntaxError。在下面的例子中計算一個違反 Python 語法的表達式。
>>>?#?Incomplete?expression
>>>?eval("5?+?7?*")
??File?"<string>",?line?1
????5?+?7?*
??????????^
SyntaxError:?unexpected?EOF?while?parsing
所以,不能把一個違反 Python 語法的表達式傳給 eval() 。在上面的例子中,我們嘗試計算一個不完整的表達式 ("5 + 7 *") 時拋出一個 SyntaxError,因為分析器不理解表達式的語法。
我們也可以把已編譯的代碼對象傳遞給 eval() 。因此可以使用函數(shù) compile(),一個內(nèi)置函數(shù),可以將輸入的字符串編譯成代碼對象或 AST 對象,這樣就可以用 eval() 來計算它。
如何使用compile()的細節(jié)超出了本文的范圍,但這里可以快速了解一下它的前三個必要參數(shù)。
- source 保存我們要編譯的源代碼。這個參數(shù)可以接受普通字符串、字節(jié)字符串和AST對象。
- filename 給出讀取代碼的文件。如果我們要使用一個基于字符串的輸入,那么這個參數(shù)的值應(yīng)該是"<string>"。
- mode 指定了我們想得到哪種編譯后的代碼。如果我們想用eval()來處理編譯后的代碼,那么這個參數(shù)應(yīng)該被設(shè)置為 "eval"。
我們可以使用 compile() 向eval()提供代碼對象,而不是普通的字符串。
>>>?#?算術(shù)運算
>>>?code?=?compile("5?+?4",?"<string>",?"eval")
>>>?eval(code)
9
>>>?code?=?compile("(5?+?7)?*?2",?"<string>",?"eval")
>>>?eval(code)
24
>>>?import?math
>>>?#?一個球體的體積
>>>?code?=?compile("4?/?3?*?math.pi?*?math.pow(25,?3)",?"<string>",?"eval")
>>>?eval(code)
65449.84694978735
如果我們使用 compile() 來編譯要傳遞給eval()的表達式,那么eval()會經(jīng)過以下步驟。
- 計算編譯后的代碼
- 返回計算的結(jié)果
如果使用基于編譯碼的輸入調(diào)用 eval() ,那么該函數(shù)會執(zhí)行計算步驟并立即返回結(jié)果。當需要多次計算同一個表達式時,這可能很方便。在這種情況下,最好預(yù)先編譯表達式,并在隨后調(diào)用 eval() 時重復(fù)使用產(chǎn)生的字節(jié)碼。
如果我們事先編譯了輸入表達式,那么連續(xù)調(diào)用eval()將運行得更快,因為我們不會重復(fù)解析和編譯的步驟。如果我們正在計算復(fù)雜的表達式,不需要的重復(fù)會導(dǎo)致高的CPU時間和過度的內(nèi)存消耗。
第二個參數(shù):globals
eval() 的第二個參數(shù) globals,可選的,字典類型,為 eval() 提供一個全局命名空間。通過 globals 告訴 eval() 在計算表達式時要使用哪些全局變量名。
全局變量名是所有那些在當前全局范圍或命名空間中可用的變量名??梢詮拇a的任何地方訪問它們。
在字典中傳遞給 globals 的所有名字在執(zhí)行時都可以提供給 eval() 。請看下面的例子,它展示了如何使用一個自定義的字典來為 eval() 提供一個全局命名空間。
>>>?x?=?100??#?一個全局變量
>>>?eval("x?+?100",?{"x":?x})
200
>>>?y?=?200??#?另一個全局變量
>>>?eval("x?+?y",?{"x":?x})
Traceback?(most?recent?call?last):
??File?"<stdin>",?line?1,?in?<module>
??File?"<string>",?line?1,?in?<module>
NameError:?name?'y'?is?not?defined
如果為 eval() 的 globals 參數(shù)提供一個自定義字典,那么 eval() 將只接受這些名字作為 globals。在這個自定義字典之外定義的任何全局變量名都不能從 eval() 內(nèi)部訪問。這就是為什么當你試圖在上述代碼中訪問 y 時,Python 會引發(fā)一個 NameError。傳遞給 globals 的字典不包括 y。
可以通過在字典中列出名字來插入 globals,然后這些名字在求值過程中就會出現(xiàn)。例如,如果在 globals 中插入了 y,那么在上面的例子中對 "x + y" 的求值將如期進行。
>>>?eval("x?+?y",?{"x":?x,?"y":?y})
300
因為把 y 添加到了自定義 globals 字典中,所以成功計算 "x + y" 的值,得到的預(yù)期返回值 300。
我們也可以提供不存在于當前全局范圍的變量名。此時需要為每個名字提供一個具體的值。eval()在運行時將把這些變量名解釋為全局變量名。
>>>?eval("x?+?y?+?z",?{"x":?x,?"y":?y,?"z":?300})
600
>>>?z
Traceback?(most?recent?call?last):
??File?"<stdin>",?line?1,?in?<module>
NameError:?name?'z'?is?not?defined
盡管z沒有在當前的全局范圍內(nèi)定義,但是這個變量在全局中的值是300,此時eval()可以訪問z,就像它是一個全局變量一樣。
globals 背后的機制是相當靈活的,可以向 globals 傳遞任何可見的變量(全局、局部、或者非局部)。還可以傳遞自定義的鍵值對,比如上面例子中的 "z": 300,那么eval() 將把它們?nèi)孔鳛槿肿兞刻幚怼?/p>
關(guān)于 globals 中的注意事項,如果我們提供給它的自定義字典不包含鍵值 "__builtins__",那么在表達式被解析之前,對內(nèi)置字典的引用將自動插入 "__builtins__" 下面。這可以確保 eval() 在計算表達式時可以完全訪問所有的 Python 內(nèi)置變量名。
下面的例子表明,即使給 globals 提供了一個空的字典,對 eval() 的調(diào)用仍然可以訪問 Python 的內(nèi)置變量名。
>>> eval("sum([2, 2, 2])", {})
6
>>> eval("min([1, 2, 3])", {})
1
>>> eval("pow(10, 2)", {})
100在上面的代碼中,我們向 globals 提供了一個空的字典 ({})。由于這個字典不包含一個叫做 "__builtins__" 的鍵,Python 會自動插入一個指向 builtins 中名字的引用。這樣,eval() 在解析表達式時就可以完全訪問所有 Python 的內(nèi)置名字。
如果調(diào)用 eval() 而沒有將自定義字典傳遞給 globals ,那么參數(shù)將默認為在調(diào)用 eval()的環(huán)境中 globals() 返回的字典:
>>>?x?=?100??#??一個全局變量
>>>?y?=?200??#?另一個全局變量
>>>?eval("x?+?y")??#?訪問兩個全局變量
300
當調(diào)用 eval() 而不提供 globals 參數(shù)時,該函數(shù)使用 globals() 返回的字典作為其全局命名空間來計算表達式。所以,在上面的例子中,我們可以自由地訪問 x 和 y,因為它們是包含在我們當前全局范圍內(nèi)的全局變量。
第三個參數(shù):locals
Python 的 eval() 第三個參數(shù) locals ,可選參數(shù),字典類型。此時這個字典包含了 eval() 在計算表達式時作為局部變量名使用的變量。
局部變量名是那些我們在一個給定的函數(shù)內(nèi)定義的名稱(變量、函數(shù)、類等等)。局部名稱只在封閉的函數(shù)內(nèi)可見。我們在編寫函數(shù)時定義這些變量名。
因為 eval() 已經(jīng)寫好了,所以不能在它的代碼或局部范圍內(nèi)添加局部變量名。然而可以向 locals 傳遞一個字典,eval()會把這些名字當作本地名字。
>>>?eval("x?+?100",?{},?{"x":?100})
200
>>>?eval("x?+?y",?{},?{"x":?100})
Traceback?(most?recent?call?last):
??File?"<stdin>",?line?1,?in?<module>
??File?"<string>",?line?1,?in?<module>
NameError:?name?'y'?is?not?defined
第一個調(diào)用 eval() 的第二個字典保存了變量 x。這個變量被 eval() 解釋為一個局部變量。換句話說,它被看作是在 eval() 中定義的一個變量。
我們可以在表達式中使用 x,并且 eval() 可以訪問它。相反,如果使用y,那么會得到一個 NameError,因為y沒有定義在 globals 命名空間或 locals 命名空間。
和 globals 一樣,可以向 locals 傳遞任何可見的變量(全局、局部或非局部)。也可以傳遞自定義的鍵值對,比如 "x"。eval()將把它們?nèi)孔鳛榫植孔兞刻幚怼?/p>
注意,要給 locals 提供一個字典,首先需要給 globals 提供一個字典。不能在 eval() 中使用關(guān)鍵字參數(shù)。
>>>?eval("x?+?100",?locals={"x":?100})
Traceback?(most?recent?call?last):
??File?"<stdin>",?line?1,?in?<module>
TypeError:?eval()?takes?no?keyword?arguments
如果在調(diào)用 eval() 時使用關(guān)鍵字參數(shù),那么拋出一個 TypeError。這是因為 eval() 不接受關(guān)鍵字參數(shù),所以在提供 locals 字典之前,需要先提供一個 globals 字典。
如果沒有給 locals 傳遞一個字典,那么它就默認為傳遞給 globals 的字典。這里有一個例子,給 globals 傳遞了一個空的字典,而 locals 沒有傳遞任何值。
>>>?x?=?100
>>>?eval("x?+?100",?{})
Traceback?(most?recent?call?last):
??File?"<stdin>",?line?1,?in?<module>
??File?"<string>",?line?1,?in?<module>
NameError:?name?'x'?is?not?defined
鑒于沒有給 locals 提供一個自定義的字典,這個參數(shù)默認為傳遞給 globals 的字典。此時eval() 無法訪問 x,因為 globals 持有一個空的字典。
globals 和 locals 之間的主要實際區(qū)別是,如果"__builtins__"鍵不存在,Python 會自動插入 globals 中。無論我們是否為 globals 提供了一個自定義的字典,這都會發(fā)生。此外,如果我們給 locals 提供了一個自定義的字典,那么在執(zhí)行 eval() 的過程中,這個字典將保持不變。
用 eval() 計算表達式
我們可以使用Python的eval()來計算任何一種Python表達式,但不包括Python語句,如基于關(guān)鍵字的復(fù)合語句或賦值語句。
當我們需要動態(tài)地計算表達式,而使用其它 Python 技術(shù)或工具會大大增加我們的開發(fā)時間和精力時,eval() 可以很方便。
在這一節(jié)中,我們將學(xué)習(xí)如何使用 Python 的 eval() 來計算布爾、數(shù)學(xué)和通用的 Python 表達式。
布爾表達式
布爾表達式 是Python表達式,當解釋器對其進行計算時返回一個真值(True 或者 False)。它們通常用在if語句中,以檢查某些條件是否為真或假。由于布爾表達式不是復(fù)合語句,我們可以使用eval()來計算它們。
>>>?x?=?100
>>>?y?=?100
>>>?eval("x?!=?y")
False
>>>?eval("x?<?200?and?y?>?100")
False
>>>?eval("x?is?y")
True
>>>?eval("x?in?{50,?100,?150,?200}")
True
我們可以用 eval() 來處理使用以下任何Python運算符的布爾表達式。
- 值比較運算符:< , > , <=, >=, ==, !=
- 邏輯(布爾)運算符:and, or, not
- 成員測試運算符:in, not in
- 身份運算符:is, is not
在所有情況下,該函數(shù)都會返回正在計算的表達式的真值。
我們思考,為什么我應(yīng)該使用eval()而不是直接使用布爾表達式呢?假設(shè)需要實現(xiàn)一個條件語句,但我們想臨時改變條件。
>>>?def?func(a,?b,?condition): ...?????if?eval(condition): ...?????????return?a?+?b ...?????return?a?-?b ... >>>?func(2,?4,?"a?>?b") -2 >>>?func(2,?4,?"a?<?b") 6 >>>?func(2,?2,?"a?is?b") 4
在func()中,使用eval()來計算所提供的條件,并根據(jù)計算的結(jié)果返回a+b或a-b。在上面的例子中,只使用了幾個不同的條件,但還可以使用任何數(shù)量的其他條件,只要堅持使用我們在func()中定義的名稱a和b。
現(xiàn)在想象一下,如果不使用Python的eval(),我們將如何實現(xiàn)這樣的東西。那會花更少的代碼和時間嗎?不可能!
數(shù)學(xué)表達式
Python 的 eval() 的一個常見用例是對基于字符串的輸入進行 math 表達式的計算。例如,創(chuàng)建一個 Python 計算器,那么可以使用 eval() 來計算用戶的輸入并返回計算結(jié)果。
下面的例子演示了如何使用eval()與數(shù)學(xué)一起進行math運算。
>>>?#?Arithmetic?operations
>>>?eval("5?+?7")
12
>>>?eval("5?*?7")
35
>>>?eval("5?**?7")
78125
>>>?eval("(5?+?7)?/?2")
6.0
>>>?import?math
>>>?#?一個圓的面積
>>>?eval("math.pi?*?pow(25,?2)")
1963.4954084936207
>>>?#?球體的體積
>>>?eval("4?/?3?*?math.pi?*?math.pow(25,?3)")
65449.84694978735
>>>?#?直角三角形的斜邊
>>>?eval("math.sqrt(math.pow(10,?2)?+?math.pow(15,?2))")
18.027756377319946
當我們使用eval()來計算數(shù)學(xué)表達式時,我們可以傳入任何種類或復(fù)雜程度的表達式,eval()會解析它們,計算它們,如果一切正常,就會給我們預(yù)期結(jié)果。
通用表達式
前面我們已經(jīng)學(xué)會了如何在布爾和 math 表達式中使用 eval() 。然而,我們可以在更復(fù)雜的 Python 表達式中使用 eval() ,這些表達式包括函數(shù)調(diào)用、對象創(chuàng)建、屬性訪問、列表推導(dǎo)式等等。
例如,可以調(diào)用一個內(nèi)置函數(shù)或用標準或第三方模塊導(dǎo)入的函數(shù)。
>>>?#?運行echo命令
>>>?import?subprocess
>>>?eval("subprocess.getoutput('echo?Hello,?World')")
'Hello,?World'
>>>?#?啟動Firefox(如果有的話)
>>>?eval("subprocess.getoutput('firefox')")
''
在這個例子中,我們使用 Python 的 eval() 來執(zhí)行一些系統(tǒng)命令。我們可以用這個功能做大量有用的事情。然而,eval()也會有一些嚴重的安全風(fēng)險,比如允許一個惡意的用戶在我們的機器中運行系統(tǒng)命令或任何任意的代碼。
以上就是Python+eval函數(shù)實現(xiàn)動態(tài)地計算數(shù)學(xué)表達式詳解的詳細內(nèi)容,更多關(guān)于Python eval計算數(shù)學(xué)表達式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Pytorch中transforms.Resize()的簡單使用
這篇文章主要介紹了Pytorch中transforms.Resize()的簡單使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07
Python Excel vlookup函數(shù)實現(xiàn)過程解析
這篇文章主要介紹了Python Excel vlookup函數(shù)實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-06-06
利用Python實現(xiàn)Excel的文件間的數(shù)據(jù)匹配功能
這篇文章主要介紹了利用Python實現(xiàn)Excel的文件間的數(shù)據(jù)匹配,本文通過一個函數(shù)實現(xiàn)此功能,通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06
tensorflow 2.0模式下訓(xùn)練的模型轉(zhuǎn)成 tf1.x 版本的pb模型實例
這篇文章主要介紹了tensorflow 2.0模式下訓(xùn)練的模型轉(zhuǎn)成 tf1.x 版本的pb模型實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨想過來看看吧2020-06-06

