首页 文章

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

提问于
浏览
97

我正在使用以下课程轻松存储我的歌曲数据 .

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 回答

  • 2

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

    • 几乎总有一种更好的方法

    • 非常危险和不安全

    • 使调试变得困难

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

    EDIT:

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

    EDIT 2:

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

    EDIT 3: 重新排序第1点和第4点

  • 25

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

    • 它违反了"Fundamental Principle of Software" . 您的来源不是可执行文件的总和 . 除了你的来源,还有 eval 的参数,必须清楚地理解 . 因此,它是最后的工具 .

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

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

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

  • 6

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

    exec 'self.Foo=val'
    

    你应该使用builtin函数 setattr

    setattr(self, 'Foo', val)
    
  • 22

    是的:

    使用Python的Hack:

    >>> 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]"
    
  • 0

    值得注意的是,对于有问题的具体问题,有几种方法可以使用 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向程序员公开了很多内部机制,你很少需要编写代码来编写代码 .

  • 11

    其他用户指出如何更改代码,使其不依赖于 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'")
    

    这里的用法显然不错;您定义输入并仅观察行为 . eval 非常适合测试 .

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

  • 4

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

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

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

  • 157

    除了@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
    

相关问题