日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達者為師

網(wǎng)站首頁 編程語言 正文

Python+eval函數(shù)實現(xiàn)動態(tài)地計算數(shù)學(xué)表達式詳解_python

作者:云朵君 ? 更新時間: 2022-11-02 編程語言

Python的?eval()?允許從基于字符串或基于編譯代碼的輸入中計算任意Python表達式。當(dāng)從字符串或編譯后的代碼對象的任何輸入中動態(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ù)的?基于字符串?或?基于編譯碼的?輸入。當(dāng)調(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

當(dāng)用一個字符串作為參數(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()?一起使用。

當(dāng)解析器不理解輸入的表達式時,也會得到一個 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é)果。當(dāng)需要多次計算同一個表達式時,這可能很方便。在這種情況下,最好預(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()?在計算表達式時要使用哪些全局變量名。

全局變量名是所有那些在當(dāng)前全局范圍或命名空間中可用的變量名。可以從代碼的任何地方訪問它們。

在字典中傳遞給 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)部訪問。這就是為什么當(dāng)你試圖在上述代碼中訪問 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。

我們也可以提供不存在于當(dāng)前全局范圍的變量名。此時需要為每個名字提供一個具體的值。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沒有在當(dāng)前的全局范圍內(nèi)定義,但是這個變量在全局中的值是300,此時eval()可以訪問z,就像它是一個全局變量一樣。

globals 背后的機制是相當(dāng)靈活的,可以向 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

當(dāng)調(diào)用?eval()?而不提供 globals 參數(shù)時,該函數(shù)使用?globals()?返回的字典作為其全局命名空間來計算表達式。所以,在上面的例子中,我們可以自由地訪問?x?和?y,因為它們是包含在我們當(dāng)前全局范圍內(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()會把這些名字當(dāng)作本地名字。

>>>?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ù)合語句或賦值語句。

當(dāng)我們需要動態(tài)地計算表達式,而使用其它 Python 技術(shù)或工具會大大增加我們的開發(fā)時間和精力時,eval()?可以很方便。

在這一節(jié)中,我們將學(xué)習(xí)如何使用 Python 的?eval()?來計算布爾、數(shù)學(xué)和通用的 Python 表達式。

布爾表達式

布爾表達式?是Python表達式,當(dāng)解釋器對其進行計算時返回一個真值(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

當(dāng)我們使用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)命令或任何任意的代碼。

原文鏈接:https://mp.weixin.qq.com/s/uN4Nif8E5WSbg4U6yFKk6Q

欄目分類
最近更新