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

发布于 2024-08-13 04:31:42 字数 560 浏览 2 评论 0 原文

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

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 的属性)而不产生这种风险?

I use the following class to easily store data of my songs.

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()

I feel that this is just much more extensible than writing out an if/else block. However, I have heard that eval is unsafe. Is it? What is the risk? How can I solve the underlying problem in my class (setting attributes of self dynamically) without incurring that risk?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

嘿嘿嘿 2024-08-20 04:31:42

是的,使用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案例中“非常危险且不安全”。对于这个具体案例来说,这可能是正确的,但一般情况下并非如此。这个问题很普遍,我列出的原因也适用于一般情况。

Yes, using eval is a bad practice. Just to name a few reasons:

  1. There is almost always a better way to do it
  2. Very dangerous and insecure
  3. Makes debugging difficult
  4. Slow

In your case you can use setattr instead:

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)

There are some cases where you have to use eval or exec. But they are rare. Using eval in your case is a bad practice for sure. I'm emphasizing on bad practice because eval and exec are frequently used in the wrong place.

Replying to the comments:

It looks like some disagree that eval is 'very dangerous and insecure' in the OP case. That might be true for this specific case but not in general. The question was general and the reasons I listed are true for the general case as well.

捎一片雪花 2024-08-20 04:31:42

使用eval很弱,但显然并不是一个的做法。

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

  2. 这通常是设计不周全的标志。动态构建的动态源代码很少有充分的理由。几乎任何事情都可以通过委托和其他面向对象设计技术来完成。

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

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

Using eval is weak, not a clearly bad practice.

  1. It violates the "Fundamental Principle of Software". Your source is not the sum total of what's executable. In addition to your source, there are the arguments to eval, which must be clearly understood. For this reason, it's the tool of last resort.

  2. It's usually a sign of thoughtless design. There's rarely a good reason for dynamic source code, built on-the-fly. Almost anything can be done with delegation and other OO design techniques.

  3. It leads to relatively slow on-the-fly compilation of small pieces of code. An overhead which can be avoided by using better design patterns.

As a footnote, in the hands of deranged sociopaths, it may not work out well. However, when confronted with deranged sociopathic users or administrators, it's best to not give them interpreted Python in the first place. In the hands of the truly evil, Python can a liability; eval doesn't increase the risk at all.

递刀给你 2024-08-20 04:31:42

是的,它是:

Hack using 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]"

Yes, it is:

Hack using Python:

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

The below code will list all tasks running on a Windows machine.

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

In Linux:

>>> eval(input())
"__import__('subprocess').Popen(['ps', 'aux'],stdout=__import__('subprocess').PIPE).communicate()[0]"
何必那么矫情 2024-08-20 04:31:42

在这种情况下,是的。相反,

exec 'self.Foo=val'

您应该使用 内置 函数 setattr

setattr(self, 'Foo', val)

In this case, yes. Instead of

exec 'self.Foo=val'

you should use the builtin function setattr:

setattr(self, 'Foo', val)
浅唱々樱花落 2024-08-20 04:31:42

值得注意的是,对于所讨论的具体问题,有几种使用 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 向程序员公开了如此多的内部机制,以至于您很少真正需要编写用于编写代码的代码。

It's worth noting that for the specific problem in question, there are several alternatives to using eval:

The simplest, as noted, is using setattr:

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

A less obvious approach is updating the object's __dict__ object directly. If all you want to do is initialize the attributes to None, then this is less straightforward than the above. But consider this:

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

This allows you to pass keyword arguments to the constructor, e.g.:

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

It also allows you to make your use of locals() more explicit, e.g.:

s = Song(**locals())

...and, if you really want to assign None to the attributes whose names are found in locals():

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

Another approach to providing an object with default values for a list of attributes is to define the class's __getattr__ method:

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

This method gets called when the named attribute isn't found in the normal way. This approach somewhat less straightforward than simply setting the attributes in the constructor or updating the __dict__, but it has the merit of not actually creating the attribute unless it exists, which can pretty substantially reduce the class's memory usage.

The point of all this: There are lots of reasons, in general, to avoid eval - the security problem of executing code that you don't control, the practical problem of code you can't debug, etc. But an even more important reason is that generally, you don't need to use it. Python exposes so much of its internal mechanisms to the programmer that you rarely really need to write code that writes code.

dawn曙光 2024-08-20 04:31:42

其他用户指出如何更改您的代码以不依赖于eval;我将提供一个使用 eval 的合法用例,该用例甚至在 CPython 中也能找到:测试

这是我在 test_unary.py< 中找到的一个示例/code> 其中测试 (+|-|~)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 对于测试来说很方便。

看看在 CPython git 存储库上执行的对 eval 的搜索; eval 测试被大量使用。

Other users pointed out how your code can be changed as to not depend on eval; I'll offer a legitimate use-case for using eval, one that is found even in CPython: testing.

Here's one example I found in test_unary.py where a test on whether (+|-|~)b'a' raises a TypeError:

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

The usage is clearly not bad practice here; you define the input and merely observe behavior. eval is handy for testing.

Take a look at this search for eval, performed on the CPython git repository; testing with eval is heavily used.

庆幸我还是我 2024-08-20 04:31:42

当使用 eval() 处理用户提供的输入时,您使用户能够Drop-to-REPL 提供类似这样的内容:

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

您可能会侥幸逃脱,但通常您不需要 在您的应用程序中任意执行代码

When eval() is used to process user-provided input, you enable the user to Drop-to-REPL providing something like this:

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

You may get away with it, but normally you don't want vectors for arbitrary code execution in your applications.

没有伤那来痛 2024-08-20 04:31:42

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

In addition to @Nadia Alramli answer, since I am new to Python and was eager to check how using eval will affect the timings, I tried a small program and below were the observations:

#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
月朦胧 2024-08-20 04:31:42

只是不要在用户可以控制输入的地方使用 eval,因为如果类型:

import os
os.system("sudo rm -rf /")

他们将擦除您的整个电脑。如果你使用Linux。

Just do not use eval where the user can control the input because if the type:

import os
os.system("sudo rm -rf /")

They will erase your entire pc. If you use Linux.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文