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

时间:2009-12-02 13:34:08

标签: python 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似乎被认为是一种不良做法,并且使用起来不安全。如果是这样,有人可以向我解释为什么,并告诉我一个更好的方法来定义上面的类?

8 个答案:

答案 0 :(得分:170)

是的,使用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)
    

    修改

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

    编辑2:

    看起来有些人不同意在OP情况下,eval“非常危险且不安全”。对于这个特定情况可能也是如此,但一般情况下并非如此。这个问题很笼统,我列出的原因也适用于一般情况。

    编辑3: 重新排序第1和第4点

答案 1 :(得分:28)

使用eval很弱,而不是明确糟糕的练习。

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

  2. 这通常是轻率设计的标志。动态源代码很少有充分的理由,即时构建。使用委托和其他OO设计技术几乎可以做任何事情。

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

  4. 作为一个脚注,在疯狂的反社会人士的手中,它可能不会很好。然而,当面对精神错乱的反社会用户或管理员时,最好不要首先给他们解释Python。在真正邪恶的手中,Python可以承担责任; eval根本不会增加风险。

答案 2 :(得分:22)

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

exec 'self.Foo=val'

您应该使用builtin函数setattr

setattr(self, 'Foo', val)

答案 3 :(得分:13)

是的,它是:

使用Python进行Hack:

css

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

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

在Linux中:

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

答案 4 :(得分:6)

值得注意的是,针对具体问题,使用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向程序员公开了很多内部机制,你很少需要编写编写代码的代码。

答案 5 :(得分:5)

其他用户指出如何更改代码,使其不依赖于eval;我将提供一个使用eval的合法用例,即使在CPython中也可以找到它: testing

以下是我在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'")

这里的用法显然不错; 您定义输入并仅观察行为。 eval对于测试非常方便。

对于eval

Take a look at this search,在CPython git存储库上执行;使用eval进行大量测试。

答案 6 :(得分:2)

eval()用于处理用户提供的输入时,您可以让用户Drop-to-REPL提供如下内容:

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

你可能会侥幸成功,但通常你不想在你的应用程序中使用arbitrary code execution的向量。

答案 7 :(得分:0)

除了@Nadia Alramli答案之外,由于我是Python的新手,并渴望检查使用eval会如何影响 timings ,我尝试了一个小程序,以下是观察结果:

#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