为什么使用“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是一个不好的做法。 只是出于几个原因:
- 几乎总是有一个更好的方法来做到这一点
- 非常危险和不安全
- 使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
很弱,不是一个明显不好的做法。
-
它违反了“软件的基本原理”。 您的来源不是可执行文件的总和。 除了你的来源之外,还有一些
eval
的论据,这些论据必须清楚地理解。 出于这个原因,这是最后的手段。 -
这通常是无意识devise的标志。 对于dynamic构build的dynamic源代码,很less有很好的理由。 使用委托和其他面向对象devise技术几乎可以做任何事情。
-
这会导致相对较慢的编译小部分代码。 可以通过使用更好的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被大量使用。