我在多个地方多次看过这个,但从来没有找到令人满意的解释,为什么会出现这种情况.
所以,希望有人会在这里提出.为什么我们(至少一般)不使用exec()
和eval()
?
编辑:我看到人们假设这个问题与网络服务器有关 - 它没有.我可以看到为什么传递给unsanitized的字符串exec
可能不好.在非网络应用程序中它是坏的吗?
通常有更清晰,更直接的方法来获得相同的效果.如果您构建一个复杂的字符串并将其传递给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可以很容易地破坏这个规则.
当你需要exec和eval时,是的,你确实需要它们.
但是,这些函数的大多数野外使用(以及其他脚本语言中的类似结构)是完全不合适的,可以用更快,更安全和更少错误的其他更简单的结构替换.
通过适当的转义和过滤,您可以安全地使用exec和eval.但那种直接寻求执行/解决问题的程序员(因为他们不了解语言提供的其他设施)并不是那种能够正确处理这种处理的编码器; 它将是一个不理解字符串处理并且盲目地连接子字符串的人,导致脆弱的不安全代码.
这是弦乐的诱惑.投掷弦乐段看起来很容易,并且欺骗天真的编码人员认为他们理解他们正在做什么.但经验表明,在某些角落(或不是那么角落)的情况下,结果几乎总是错误的,通常具有潜在的安全隐患.这就是为什么我们说eval是邪恶的.这就是为什么我们说regex-for-HTML是邪恶的.这就是我们推动SQL参数化的原因.是的,你可以通过手动字符串处理得到所有这些东西......但除非你已经明白为什么我们说这些东西,否则你很可能不会.
eval()和exec()可以促进延迟编程.更重要的是,它表示正在执行的代码可能未在设计时写入,因此未经过测试.换句话说,您如何测试动态生成的代码?特别是跨浏览器.
安全之余,eval
并exec
经常被标记因为它们诱导复杂不可取.当你看到一个eval
电话时,你经常不知道它背后的真实情况,因为它会影响通常在变量中的数据.这使得代码更难阅读.
调用解释器的全部功能是一个重武器,应该只保留用于非常棘手的情况.但是,在大多数情况下,最好避免使用更简单的工具.
也就是说,像所有概括一样,要警惕这一点.在某些情况下,exec和eval可能很有价值.但是你必须有充分的理由使用它们.有关一个可接受的用途,请参阅此帖
与大多数答案在这里所说的相反,exec实际上是在Python中构建超完备装饰器的配方的一部分,因为您可以精确复制有关装饰函数的所有内容,为文档等目的生成相同的签名.它是广泛使用的装饰器模块(http://pypi.python.org/pypi/decorator/)功能的关键.exec/eval必不可少的其他情况是构造任何类型的"解释Python"类型的应用程序,例如Python解析的模板语言(如Mako或Jinja).
因此,这些功能的存在并不是"不安全"应用程序或库的直接标志.以天真的javascripty方式使用它们来评估传入的JSON或其他东西,是的,这是非常不安全的.但与往常一样,它一切都在你使用它的方式,这些是非常重要的功能.
我eval()
过去曾经(并且仍然不时地)用于在快速和肮脏的操作期间按摩数据.它是可用于完成工作的工具包的一部分,但绝不能用于您计划在生产中使用的任何内容,例如任何命令行工具或脚本,因为其他答案中提到了所有原因.
您无法相信您的用户 - 做正确的事情.在大多数情况下,他们会,但你必须期望他们做你从未想过的所有事情,并找到你从未想到的所有错误.这正是eval()
从工具到责任的地方.
一个完美的例子就是使用Django构建一个QuerySet
.传递给查询的参数接受关键字参数,如下所示:
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()
是OK的小任务,测试,真正临时的事情,但糟糕的永久使用,因为那里几乎可以肯定总有更好的方式来做到这一点!
在可能运行用户输入的上下文中允许这些功能是一个安全问题,实际工作的清理程序很难编写.