为什么使用“ eval”是一种不好的做法?

我使用下面的类轻松地存储我的歌曲的数据。

class Song:
"""The class to store the details of each song"""
attsToStore=('Name', 'Artist', 'Album', 'Genre', 'Location')
def __init__(self):
for att in self.attsToStore:
exec 'self.%s=None'%(att.lower()) in locals()
def setDetail(self, key, val):
if key in self.attsToStore:
exec 'self.%s=val'%(key.lower()) in locals()

我觉得这比编写 if/else块可扩展得多。但是,我听说 eval是不安全的。是吗?有什么风险?如何解决类中的潜在问题(动态设置 self的属性)而不引起这种风险?

66527 次浏览

是的,使用 eval是一种不好的做法。仅举几个原因:

  1. 几乎总有更好的方法来做到这一点
  2. 非常危险,没有安全感
  3. 让调试变得困难
  4. 慢点

在您的情况下,您可以使用 Setattr代替:

class Song:
"""The class to store the details of each song"""
attsToStore=('Name', 'Artist', 'Album', 'Genre', 'Location')
def __init__(self):
for att in self.attsToStore:
setattr(self, att.lower(), None)
def setDetail(self, key, val):
if key in self.attsToStore:
setattr(self, key.lower(), val)

有些情况下,你必须使用 evalexec。但它们很稀有。在您的案例中使用 eval肯定是一种不好的做法。我之所以强调不好的练习,是因为 evalexec经常被用在错误的地方。

对评论的答复:

似乎有些人不同意 eval在 OP 中是“非常危险和不安全的”。这对于这个特定的情况可能是正确的,但不是一般情况。这个问题是一般性的,我列出的理由对于一般情况也是正确的。

在这种情况下,是的,而不是

exec 'self.Foo=val'

你应使用 天生的函数 setattr:

setattr(self, 'Foo', val)

使用 eval很弱,不是一个明显的 很糟糕实践。

  1. 它违反了“软件的基本原则”。您的源代码不是可执行代码的总和。除了您的来源之外,还有 eval的参数,必须清楚地理解它。因此,它是最后的手段。

  2. 这通常是设计欠考虑的表现。对于动态源代码,动态构建的原因很少。几乎任何事情都可以通过代理和其他面向对象设计技术来完成。

  3. 它导致对小段代码的动态编译相对较慢。这种开销可以通过使用更好的设计模式来避免。

作为一个脚注,在精神错乱的反社会者手中,这可能不会有好结果。然而,当面对精神错乱的反社会用户或管理员时,最好一开始就不要给他们解释 Python。在真正邪恶的人手中,Python 可能成为一种负担; eval根本不会增加风险。

值得注意的是,对于所讨论的具体问题,有几种使用 eval的替代方法:

如前所述,最简单的方法是使用 setattr:

def __init__(self):
for name in attsToStore:
setattr(self, name, None)

一种不太明显的方法是直接更新对象的 __dict__对象。如果您想要做的只是初始化 None的属性,那么这就没有上面那么简单了。但是想想这个:

def __init__(self, **kwargs):
for name in self.attsToStore:
self.__dict__[name] = kwargs.get(name, None)

这允许您向构造函数传递关键字参数,例如:

s = Song(name='History', artist='The Verve')

它还允许你更明确地使用 locals(),例如:

s = Song(**locals())

... 而且,如果你真的想把 None赋给在 locals()中找到名字的属性:

s = Song(**dict([(k, None) for k in locals().keys()]))

为属性列表提供具有默认值的对象的另一种方法是定义类的 __getattr__方法:

def __getattr__(self, name):
if name in self.attsToStore:
return None
raise NameError, name

当以正常方式找不到命名属性时,将调用此方法。与简单地在构造函数中设置属性或更新 __dict__相比,这种方法稍微不那么简单,但它的优点是除非存在属性,否则不会实际创建属性,这可以大大减少类的内存使用。

所有这一切的要点是: 一般来说,有很多原因可以避免 eval-执行您不能控制的代码的安全问题,无法调试的代码的实际问题等等。但是一个更重要的原因是,一般来说,你不需要使用它。Python 向程序员公开了如此多的内部机制,以至于您很少真正需要编写编写代码的代码。

是的:

使用 Python 编程:

>>> eval(input())
"__import__('os').listdir('.')"
...........
...........   #dir listing
...........

下面的代码将列出在 Windows 计算机上运行的所有任务。

>>> eval(input())
"__import__('subprocess').Popen(['tasklist'],stdout=__import__('subprocess').PIPE).communicate()[0]"

Linux:

>>> eval(input())
"__import__('subprocess').Popen(['ps', 'aux'],stdout=__import__('subprocess').PIPE).communicate()[0]"

其他用户指出了如何更改代码以使其不依赖于 eval; 我将提供使用 eval的合法用例,即使在 CPython: 测试中也可以找到这种用例。

下面是我在 test_unary.py中发现的一个例子,其中一个关于 (+|-|~)b'a'是否产生 TypeError的测试:

def test_bad_types(self):
for op in '+', '-', '~':
self.assertRaises(TypeError, eval, op + "b'a'")
self.assertRaises(TypeError, eval, op + "'a'")

这里的用法显然不是坏习惯; 你定义输入和仅仅观察行为。

请看一下在 CPython git 存储库上执行的 eval搜索 ; 大量使用 eval 进行测试。

当使用 eval()处理用户提供的输入时,可以让用户使用 降到 REPL提供类似下面这样的内容:

"__import__('code').InteractiveConsole(locals=globals()).interact()"

您可能会侥幸逃脱,但通常您不希望在应用程序中使用 任意代码执行任意代码执行的矢量。

除了@Nadia Alramli 的回答之外,由于我是 Python 的新手,并且渴望了解使用 eval将如何影响 时机,所以我尝试了一个小程序,下面是我的观察结果:

#Difference while using print() with eval() and w/o eval() to print an int = 0.528969s per 100000 evals()


from datetime import datetime
def strOfNos():
s = []
for x in range(100000):
s.append(str(x))
return s


strOfNos()
print(datetime.now())
for x in strOfNos():
print(x) #print(eval(x))
print(datetime.now())


#when using eval(int)
#2018-10-29 12:36:08.206022
#2018-10-29 12:36:10.407911
#diff = 2.201889 s


#when using int only
#2018-10-29 12:37:50.022753
#2018-10-29 12:37:51.090045
#diff = 1.67292