Python的eval()函数是什么?

342

我正在阅读一本关于Python的书,它一直在使用代码 eval(input('blah'))

我阅读了文档,理解了它,但仍然不知道它如何改变input()函数。

它是做什么用的?可以有人解释一下吗?


4
Eval函数旨在执行并解释作为参数传递给它的字符串内容作为Python代码。例如,当x=1时,打印(eval('x+1'))将输出2。这种方法的缺点是用户可以编写破坏性代码,从而导致混乱的情况。不过,你可以通过在eval函数中传递全局和局部参数来限制用户访问许多变量和方法。 - ATIF IBAD KHAN
2
这本书中的代码是由作者将危险(但易于编写)的Python 2.x代码机械翻译成了危险(完全相同的方式,完全相同的危险性)的Python 3.x代码而存在的。使用2.x代码是因为它是一种方便的方法,例如,可以输入整数而无需解析字符串。然而,这是语言中的一个严重的设计缺陷。正确编写的代码将解析字符串,因为用户输入永远不能被信任。我希望我们能找出这是哪本书,以便警告每个人不要使用它 - Karl Knechtel
12个回答

309

eval函数允许Python程序在其内部运行Python代码。

示例(eval在交互式shell中使用):

>>> x = 1
>>> eval('x + 1')
2
>>> eval('x')
1

28
哈哈,那只是一个简单的例子,但你可以让用户输入任意命令,然后让Python执行它。所以你可以让用户输入一个命令字符串,然后让Python将其作为代码运行。例如:eval("import('os').remove('file')")。 - BYS2
66
直到你找到它的用处,它才显得有用。像codepad.org这样的网站上使用它可以让你在测试环境中执行脚本。eval()还可用于执行高度动态的代码,但在使用前应充分了解安全和性能风险。 - George Cummins
8
@GeorgeCummins,codepad.org没有使用eval,也不能通过eval实现它的功能。 - Mike Graham
17
codepag.org 在一个沙盒中运行所有内容:使用虚拟机的 chroot 监狱和 ptrace 检查,以防止恶意代码做任何坏事。这比简单的 eval 复杂得多。另外,eval 是 Python 特有的。codepad 支持许多语言。 - FogleBird
4
@GeorgeCummins,codepad运行一个非常复杂的系统来安全地运行任意程序。“eval”除了不安全之外,还不能像codepad那样运行整个程序,因为它只能评估单个表达式。 - Mike Graham
显示剩余6条评论

180

eval()函数将一个字符串作为代码来执行。之所以有很多人警告你不要使用它,是因为用户可以将其用作在计算机上运行代码的选项。如果你导入了os模块并使用eval(input()),那么一个人可以在input()中输入os.system('rm -R *'),这将删除你主目录中的所有文件。(假设你的系统是Unix)。使用eval()存在安全漏洞。如果需要将字符串转换为其他格式,请尝试使用像int()这样的函数。


19
你的意思是使用evalinput()结合会存在安全漏洞。不要将input()放在eval语句中,这样就可以避免问题。 - tim-phillips
28
@Rohmer,不安全的数据可能来自任何地方:网络请求、表单输入字段、文件读取等,不仅限于控制台输入。即使你自己编写了文件,它仍然可能包含最初来自不受信任来源的输入。因此,在许多情况下,“eval”存在安全问题。 - sanderd17
4
由于“input”通常从控制台获取数据,用户可以随时退出程序并键入“rm -R *”。 - c z
1
考虑使用 eval 从请求数据中执行代码的情况。这将允许用户在服务器上执行任意代码。这就是为什么 eval 是危险的原因。 - kahveciderin
在使用eval()时存在负面影响,但是在代码中是否有完全安全的用法呢?我也想看到它的积极面。我在我的代码中只使用了一次eval(),并且非常高兴这个函数存在。 - Robin

73
如文档所述, eval()还有globalslocals关键字参数,可用于限制通过eval函数可用的函数。例如,如果您加载了一个新的Python解释器,则locals()globals()将是相同的,并且看起来像这样:

>>> globals()
{'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__doc__': None,
 '__spec__': None, '__builtins__': <module 'builtins' (built-in)>,
 '__package__': None, '__name__': '__main__'}

builtins模块中肯定有一些函数会对系统造成重大破坏。但是我们可以阻止任何我们不想要的东西。比如说,我们想构建一个列表来表示系统上可用核心的域。对于我来说,我有8个核心,所以我想要一个列表[1, 8]

>>> from os import cpu_count
>>> eval('[1, cpu_count()]')
[1, 8]

同样,所有的__builtins__也是可用的。
>>> eval('abs(-1)')
1

让我们尝试阻止访问任何全局变量:

>>> eval('[1, cpu_count()]', {'__builtins__':None}, {})
TypeError: 'NoneType' object is not subscriptable

我们已经有效地阻止了所有的__builtins__函数,从而为我们的系统带来了一定程度的保护。此时,我们可以开始添加我们想要暴露的函数。

>>>from os import cpu_count
>>>exposed_methods = {'cpu_count': cpu_count}
>>>eval('cpu_count()', {'__builtins__':None}, exposed_methods)
8
>>>eval('abs(cpu_count())', {'__builtins__':None}, exposed_methods)
TypeError: 'NoneType' object is not subscriptable

现在我们有了cpu_count函数,可以阻止我们不想要的一切。在我看来,这是非常强大的,显然从其他答案的范围来看,这不是一个常见的实现。像这样的东西有很多用途,只要处理得当,我个人认为eval可以安全地用于很大的价值。

N.B.

这些kwargs的另一个酷炫之处是,您可以开始使用代码的速记。假设您使用eval作为管道的一部分来执行一些导入的文本。文本不需要精确的代码,它可以遵循一些模板文件格式,并且仍然可以执行任何您想要的内容。例如:

>>> from os import cpu_count
>>> eval('[1,cores]', {'__builtins__': None}, {'cores': cpu_count()})
[1, 8]

2
这并不像一个监狱一样工作。您可以使用以下内容获取内置对象,仍然可以运行危险代码(运行rm -rf *):eval("[a for a in ().__class__.__bases__[0].__subclasses__() if 'catch_warnings' in a.__name__][0]()._module.__builtins__['__import__']('os').system('rm -rf *')", {"__builtins__": None}) - muddyfish
1
尝试以这种方式“沙盒”或“监禁”eval的代码绝对不安全。 - Karl Knechtel

29
在Python 2.x中,input(...)等同于eval(raw_input(...))。在Python 3.x中,raw_input被重命名为input,这可能导致了您的困惑(您可能正在查看Python 2.x中input的文档)。此外,在Python 3.x中,eval(input(...))将正常工作,但在Python 2中会引发TypeError
在这种情况下,eval用于将input返回的字符串强制转换为表达式并进行解释。一般来说,这被认为是不好的做法。

或者这是一本Python 3.x的书,其中input的意思就像2.x中的raw_input - dan04
1
是的,在我写下我的初始答案之后,我也想到了这一点,而且显然就是这种情况。 - Zach Kelling

7

eval(),顾名思义,评估传递的参数。

raw_input()在Python 3.x版本中已更改为input()。因此,在Python 2.x版本中,eval()的最常见用法示例是提供与input()相同的功能。

raw_input将用户输入的数据作为字符串返回,而input则计算输入数据的值并返回该值。

eval(input("bla bla"))因此复制了Python 2.x中input()的功能,即评估用户输入的数据。

简而言之:eval()评估传递给它的参数,因此eval('1 + 1')返回2。


6

eval() 的一个有用的应用是从字符串中评估 Python 表达式。例如,从文件加载字典的字符串表示:

running_params = {"Greeting": "Hello, "}
fout = open("params.dat", 'w')
fout.write(repr(running_params))
fout.close()

将其作为变量读取并进行编辑:

fin = open("params.dat", 'r')
diction = eval(fin.read())
diction["Greeting"] += "World!"
fin.close()
print diction

输出:

{'Greeting': 'Hello, World!'}

9
这如何回答询问“eval是什么”的问题? - jkd

6

以下是关于读取一行并解释它的一个可能误导性示例。

尝试使用eval(input())命令,并输入"1+1" - 这将打印出2。Eval用于计算表达式。


1
为什么我需要在引号之间输入它?输入是一个字符串,将其传递给eval,而不是执行代码,因此如果我只是键入1 + 1,那么应该没问题... ¿? - user4396006
问题在于您混合使用了P2.x和3.x。在Python 2中,您的代码可以工作,但是eval两次没有意义。在Python 3中,它不起作用,并返回一个字符串。 - user4396006

6
eval() 函数将传递的字符串作为 Python 表达式进行评估,并返回结果。例如,eval("1 + 1") 解释并执行表达式 "1 + 1" 并返回结果 (2)。
你可能会感到困惑的一个原因是因为你引用的代码涉及一定程度的间接性。内部函数调用 (input) 首先被执行,因此用户看到 "blah" 提示符。假设他们回答 "1 + 1" (为了清晰起见添加引号,请在运行程序时不要输入引号),则输入函数返回该字符串,然后将其传递给外部函数 (eval),它解释该字符串并返回结果 (2)。
阅读有关 eval 的更多信息请单击此处

6

如果你想将评估字符串限制为简单字面值,另一个选项是使用 ast.literal_eval()。以下是一些示例:

import ast

# print(ast.literal_eval(''))          # SyntaxError: unexpected EOF while parsing
# print(ast.literal_eval('a'))         # ValueError: malformed node or string
# print(ast.literal_eval('import os')) # SyntaxError: invalid syntax
# print(ast.literal_eval('1+1'))       # 2: but only works due to a quirk in parser
# print(ast.literal_eval('1*1'))       # ValueError: malformed node or string
print(ast.literal_eval("{'a':1}"))     # {'a':1}

文档中得知:
安全地评估表达式节点或包含Python字面值或容器显示的字符串。提供的字符串或节点可能由以下Python文字结构组成:字符串、字节、数字、元组、列表、字典、集合、布尔值和None。
这可用于安全地评估包含来自不受信任来源的Python值的字符串,而无需解析这些值。它不能评估涉及运算符或索引的任意复杂表达式。
至于为什么如此有限,从邮件列表得知:
允许具有文字的操作符表达式是可能的,但比当前实现更复杂。一个简单的实现是不安全的:你可以使用很少的努力就可以诱使基本上无界的CPU和内存使用(尝试“9**9**9”或“[None] * 9**9”)。
至于有用性,该功能对于将文字值和容器作为repr()串行化类似于但更强大的格式“读回”非常有用。

1
ast.literal_eval不支持运算符,与您的'1+1'示例相反。尽管如此,它确实支持列表、数字、字符串等,因此是常见eval用例的良好替代品。 - benjimin
@benjimin 哦,你说得对 - 它只是接受1+1的怪癖!https://dev59.com/A5zha4cB1Zd3GeqPFXfA - Brian Burns
1
简短版:它支持+-,因为它们需要支持复数的语法,而且特别处理这个逻辑太难了(或者至少需要额外的工作)。 - Karl Knechtel

5
如果用户输入数字值,input() 函数将返回字符串类型。
>>> input('Enter a number: ')
Enter a number: 3
>>> '3'
>>> input('Enter a number: ')
Enter a number: 1+1
'1+1'

eval()函数将计算返回值(或表达式),该值是一个字符串,并返回整数/浮点数。

>>> eval(input('Enter a number: '))
Enter a number: 1+1
2
>>>
>>> eval(input('Enter a number: '))
Enter a number: 3.14
3.14

然而,在这里最好使用更具体的工具,例如int()float()

>>> float(input('Enter a number: '))
Enter a number: 3.14
3.14

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接