我在多个地方多次看过这个,但从来没有找到令人满意的解释,为什么会出现这种情况。
所以,希望有人会在这里提出。为什么我们(至少一般)不使用exec()
和eval()
?
exec
的未经过类型化的字符串可能不好。在非网络应用程序中是不是很糟糕?
答案 0 :(得分:19)
通常有更清晰,更直接的方法来获得相同的效果。如果您构建一个复杂的字符串并将其传递给exec,则代码很难遵循,并且难以测试。
示例:我编写的代码读取字符串键和值,并在对象中设置相应的字段。它看起来像这样:
for key, val in values:
fieldName = valueToFieldName[key]
fieldType = fieldNameToType[fieldName]
if fieldType is int:
s = 'object.%s = int(%s)' % (fieldName, fieldType)
#Many clauses like this...
exec(s)
对于简单的情况,该代码并不太可怕,但随着新类型的出现,它变得越来越复杂。当有错误时,他们总是在调用exec时触发,因此堆栈跟踪并没有帮助我找到它们。最后,我切换到一个稍微长一点,不那么聪明的版本,明确地设置每个字段。
代码清晰度的第一条规则是,通过仅查看附近的行,您的代码的每一行都应该易于理解。这就是为什么不鼓励使用goto和全局变量的原因。 exec和eval可以很容易地破坏这条规则。
答案 1 :(得分:13)
当你需要exec和eval时,是的,你确实需要它们。
但是,大多数这些函数的野外使用(以及其他脚本语言中的类似结构)都是完全不合适的,可以用更快,更安全和更少错误的其他更简单的结构替换。
你可以,通过适当的转义和过滤,安全地使用exec和eval。但那种直接寻求执行/解决问题的程序员(因为他们不了解语言提供的其他设施)并不是那种能够正确处理这种处理的编码器;它会成为一个不理解字符串处理并盲目地连接子字符串的人,导致脆弱的不安全代码。
这是弦乐的诱惑。在周围投掷字符串片段看起来很容易,并且让天真的编码人员认为他们理解他们正在做什么。但经验表明,在某些角落(或不是那么角落)的情况下,结果几乎总是错误的,通常具有潜在的安全隐患。这就是为什么我们说eval是邪恶的。这就是为什么我们说regex-for-HTML是邪恶的。这就是我们推动SQL参数化的原因。是的,你可以通过手动字符串处理获得所有这些东西......但除非你已经理解我们为什么说这些东西,否则你将不会。
答案 2 :(得分:11)
eval()和exec()可以促进延迟编程。更重要的是,它表示正在执行的代码可能未在设计时写入,因此未经过测试。换句话说,您如何测试动态生成的代码?特别是跨浏览器。
答案 3 :(得分:10)
除了安全性之外,eval
和exec
通常被标记为不受欢迎,因为它们会引起复杂性。当您看到eval
调用时,您通常不知道其后面发生了什么,因为它会对通常位于变量中的数据起作用。这使得代码更难阅读。
调用解释器的全部功能是一项重要的武器,只能用于非常棘手的情况。但是,在大多数情况下,最好避免使用更简单的工具。
那就像所有的概括一样,要警惕这一点。在某些情况下,exec和eval可能很有价值。但是你必须有充分的理由使用它们。有关一个可接受的用途,请参阅this post。
答案 4 :(得分:8)
与大多数答案在这里所说的相反,exec实际上是在Python中构建超完整装饰器的配方的一部分,因为您可以精确复制有关装饰函数的所有内容,为文档和目的生成相同的签名这样。它是广泛使用的装饰器模块(http://pypi.python.org/pypi/decorator/)功能的关键。 exec / eval必不可少的其他情况是构造任何类型的“解释Python”类型的应用程序,例如Python解析的模板语言(如Mako或Jinja)。
因此,这些功能的存在并不是“不安全”应用程序或库的直接标志。以天真的javascripty方式使用它们来评估传入的JSON或其他东西,是的,这是非常不安全的。但与往常一样,它一直在你使用它的方式,这些是非常重要的功能。
答案 5 :(得分:6)
我过去曾经使用eval()
(并且仍然不时地做)在快速和肮脏的操作过程中按摩数据。它是可用于完成工作的工具包的一部分,但永远不会用于您计划在生产中使用的任何内容,例如任何命令行工具或脚本,因为所有其他答案中提到的原因。
您无法相信您的用户 - 做正确的事情。在大多数情况下,他们会,但你必须期望他们做你从未想过的所有事情,并找到你从未想到的所有错误。这正是eval()
从工具变为责任的地方。
在构建QuerySet
时,一个完美的例子就是使用Django。传递给查询的参数接受关键字参数,如下所示:
results = Foo.objects.filter(whatever__contains='pizza')
如果您以编程方式分配参数,您可能会考虑这样做:
results = eval("Foo.objects.filter(%s__%s=%s)" % (field, matcher, value))
但是总有一种更好的方法不使用eval()
,它通过引用传递字典:
results = Foo.objects.filter( **{'%s__%s' % (field, matcher): value} )
通过这种方式,它不仅性能更快,而且更安全,更Pythonic。
故事的道德?
使用eval()
确定用于小型任务,测试和真正临时的事情,但糟糕用于永久使用,因为几乎可以肯定更好这样做的方法!
答案 6 :(得分:5)
在可能运行用户输入的上下文中允许这些功能是一个安全问题,实际工作的清理程序很难编写。
答案 7 :(得分:3)
s = "import shutil; shutil.rmtree('/nonexisting')"
eval(s)
现在假设有人可以从Web应用程序控制s,例如。
请勿尝试在您的计算机上执行此操作
答案 8 :(得分:3)
同样的原因你不应该以root用户身份登录:用脚射击自己太容易了。
答案 9 :(得分:2)
原因#1:一个安全漏洞(即编程错误......我们不能声称可以避免这些错误)并且您只是让用户访问服务器的shell。
答案 10 :(得分:2)
在交互式解释器中尝试此操作,看看会发生什么:
>>> import sys
>>> eval('{"name" : %s}' % ("sys.exit(1)"))
当然,这是一个极端的案例,但要防止这样的事情可能会很棘手。