为什么使用“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似乎被认为是不好的做法,并且不安全。 如果是这样,谁能向我解释为什么,并给我一个更好的方式来定义上面的类?

是的,使用eval是一个不好的做法。 只是出于几个原因:

  1. 几乎总是有一个更好的方法来做到这一点
  2. 非常危险和不安全
  3. 使debugging困难

在你的情况下,你可以使用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) 

编辑:

在某些情况下,您必须使用eval或exec。 但他们是罕见的。 在你的情况下使用eval肯定是一个不好的做法。 我强调不好的做法,因为eval和exec经常被用在错误的地方。

编辑2:

在OP案例中,eval是“非常危险和不安全的”,看起来有些不同意。 对于这个具体的情况可能是这样,但不是一般的情况。 问题是一般的,我列出的理由也适用于一般情况。

编辑3:重新sorting点1和4

使用eval很弱,不是一个明显不好的做法。

  1. 它违反了“软件的基本原理”。 您的来源不是可执行文件的总和。 除了你的来源之外,还有一些eval的论据,这些论据必须清楚地理解。 出于这个原因,这是最后的手段。

  2. 这通常是无意识devise的标志。 对于dynamic构build的dynamic源代码,很less有很好的理由。 使用委托和其他面向对象devise技术几乎可以做任何事情。

  3. 这会导致相对较慢的编译小部分代码。 可以通过使用更好的devise模式避免的开销。

作为一个脚注,在疯狂的反社会人士手中,它可能不会奏效。 但是,当面对疯狂的社交用户或pipe理员时,最好不要给他们解释Python。 在真正的邪恶手中,Python可以承担责任; eval不会增加风险。

在这种情况下,是的。 代替

 exec 'self.Foo=val' 

你应该使用内build函数setattr

 setattr(self, 'Foo', val) 

是的:

使用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有几种select:

如上所述,最简单的方法是使用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) 

这允许您将关键字parameter passing给构造函数,例如:

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

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

 s = Song(**locals()) 

…如果你真的想把None分配给在locals()中find的属性:

 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__稍微简单一些,但是它的优点是不需要实际创build属性,除非存在,这可以大大减less类的内存使用量。

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

其他用户指出你的代码可以改变为不依赖于eval ; 我将提供使用eval的合法用例,即使在CPython中也可以find它: testing

下面是我在test_unary.py中find的一个例子,其中(+|-|~)b'a'引发TypeError

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

这里的用法显然不错, 你定义input ,只是观察行为。 eval方便testing。

看看这个search eval ,在CPython git仓库上执行; eval的testing被大量使用。