我正在使用下面的类轻松存储我的歌曲的数据。
1 2 3 4 5 6 7 8 9
| 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似乎被认为是一种不好的做法,使用起来不安全。如果是这样的话,有人能向我解释为什么,并向我展示一种更好的定义上述类的方法吗?
- 你是怎么知道exec/eval的,却仍然不知道setattr的?
- 我相信这是从一篇比较python和lisp的文章中学到的,而不是关于eval的。
是的,使用eval是一种不好的做法。举几个原因:
几乎总是有更好的方法
非常危险和不安全
使调试困难
缓慢的
在您的情况下,您可以使用setattr代替:
1 2 3 4 5 6 7 8 9
| 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:
看起来有些人不同意评估是"非常危险和不安全"的行动。对于这个特定的情况,这可能是正确的,但一般来说不是。这个问题是一般性的,我列出的理由对一般情况也是正确的。
编辑3:重新排序点1和4
- 我比__dic__更喜欢这个,因为它更明确地说明了你打算做什么。
- 在这种情况下,评估是一种不好的做法。但逃避不是一种普遍的坏习惯。有些情况下,使用eval/exec/execfile非常好。
- 谢谢,我刚把代码改成使用setattr。
- -1:"非常危险和不安全"是错误的。另外三个非常清楚。请重新排序,使2和4是前两个。只有当你被邪恶的反社会者包围,他们在寻找颠覆你的应用程序的方法时,你才没有安全感。
- @S.lott,不安全是避免评估/执行的一个非常重要的原因。许多像网站这样的应用程序应该格外小心。以期望用户输入歌曲名称的网站中的op示例为例。它迟早会被开发利用。即使是一个无辜的输入,比如:让我们玩得开心。将导致语法错误并暴露该漏洞。
- 如果eval是邪恶的,为什么它首先存在?
- @Selinap,eval有实际的用例,但它们是罕见的和特殊的。在OP案例中,很明显eval是一个糟糕且不安全的选择。如果存在不意味着我们应该盲目使用的东西。
- 我同意S.Lott的观点:执行语句中没有用户数据,因此没有理由认为它不安全。
- @丹尼斯,你说得对,操作包没有用户数据。但问题仍然是一般性的:"在python中使用eval是一种坏做法吗?"
- @nadia alramli:用户输入和eval之间没有任何关系。基本上是错误设计的应用程序基本上是错误设计的。eval不是错误设计的根本原因,而是被零除或试图导入一个已知不存在的模块。eval不是不安全的。应用程序不安全。
- S.lott的观点是正确的。从根本上说,江户十一〔一〕并不坏。它的设计几乎总是要受到指责。如果您的设计不能保证您不受恶意用户输入的影响,那么eval可能不是您应该担心的。
- @JeffJose:事实上,它从根本上说是坏的/坏的,因为它把未经统计的数据当作代码来处理(这就是为什么存在XSS、SQL注入和堆栈崩溃)。@S.lott:"只有当你被邪恶的反社会分子包围,他们在寻找颠覆你的应用程序的方法时,才是不安全的。"酷,比如说你做了一个程序calc,加上数字,它执行print(eval("{} + {}".format(n1, n2)))并退出。现在您将这个程序与一些操作系统一起分发。然后,有人制作了一个bash脚本,从一个股票站点中提取一些数字,并使用calc将它们相加。繁荣?
- @S.lott:如果并且仅当使用eval的方法不执行作为副作用的疯狂代码时,最好说eval是健全的……假设我有一个用JavaScript编写的JSON解码器…我想传递任何东西给它,我不想在每次调用它时浪费时间编写卫生处理代码(如果函数刚刚返回eval(input),情况就会是这样)。
- 看,每个使用python的人都不会在主要关注安全性的地方编写Web脚本或应用程序。所以,尽管我同意通常有更好的方法,比如说使用setattr(),并且应该努力找到更好的设计,但我绝对不同意eval/exec本质上是"非常危险和不安全的"。它们之所以可用是有充分的理由的,而且它们所提供的能力是解释语言(如python)最酷和最强大的特性之一。
- @马蒂诺:与其他替代品相比,它本身就非常危险和不安全。如果/当您没有其他选择时,没有可供比较的内容,因此可以使用。
- 我不知道纳迪亚的主张为什么如此有争议。在我看来很简单:eval是代码注入的载体,在某种程度上是危险的,而大多数其他的python函数则不是。这并不意味着你不应该使用它,但我认为你应该明智地使用它。
- 我同意欧文的观点。说eval是"不危险或不安全的",有点像说"手工构建的SQL不危险或不安全的"。当然,可能很少有必要使用它的情况,或者有时你根本不关心它,因为安全对你的应用程序来说不是问题,但这两种情况都不能否定evalh因为它能够非常容易地使您的应用程序受到外部攻击。""很容易使我的应用程序容易受到外部攻击"听起来像是一个合理的条件,认为某些东西"非常危险和不安全"。
- 嗨,谢谢你的回答。如果速度慢,为什么?
使用eval很弱,不是一个明显的坏做法。
它违反了"软件的基本原理"。您的源代码不是可执行文件的总和。除了你的资料来源之外,还有一些关于eval的论点,必须清楚地理解。因此,它是最后的手段。
这通常是设计不周密的标志。动态源代码几乎没有一个好的理由,它是动态构建的。几乎任何事情都可以通过授权和其他OO设计技术完成。
它导致代码片段的动态编译相对缓慢。使用更好的设计模式可以避免的开销。
作为脚注,在神经错乱的反社会者的手中,这可能行不通。然而,当面对神经错乱的反社会用户或管理员时,最好不要首先给他们解释python。在真正邪恶的手中,Python可以承担责任;eval根本不会增加风险。
- 我很好奇你认为一个反社会者会对埃瓦尔做什么?为什么这会是一个更无辜的程序员也不会偶然发现的用例呢?
- @欧文S。关键是这个。人们会告诉你,eval是某种"安全漏洞"。就好像python本身不只是一堆任何人都可以修改的解释源代码。当面对"eval是一个安全漏洞"时,你只能假设它是反社会者手中的一个安全漏洞。普通的程序员只需修改现有的python源代码并直接导致他们的问题。不是间接地通过eval魔法。
- 好吧,我可以告诉你为什么我会说eval是一个安全漏洞,它与作为输入的字符串的可靠性有关。如果这个字符串全部或部分来自外部世界,如果您不小心的话,有可能对您的程序进行脚本攻击。但这是外部攻击者的精神错乱,而不是用户或管理员的精神错乱。
- @欧文斯:"如果那根绳子全部或部分来自外部世界",那就常常是错误的。这不是"小心"的事情。它是黑白相间的。如果文本来自用户,则永远不可信任。关心并不是真正的一部分,它绝对不可靠。否则,文本来自开发人员、安装人员或管理员,可以信任。
- 不受信任的字符串通常可以通过适当的转义转换为受信任的字符串。这就是我所说的"小心"。我们并不反对,正如你所希望的那样。-)
- @欧文斯:一系列不受信任的python代码不可能让它变得可信。我同意你所说的大部分内容,除了"小心"部分。这是一个非常清晰的区别。来自外部世界的代码是不可靠的。阿法克,没有多少逃逸或过滤可以清除它。如果您有某种使代码可接受的转义函数,请共享。我认为这样的事情是不可能的。例如,while True: pass将很难用某种逃逸来清理。
- 假设您从外部世界接受的是一个字符串,而不是任意代码。在这种情况下,小心意味着不要将字符串直接拼接到要进行eval的代码中,或者如果要将字符串中的引号合并到传递给eval的代码中,请确保正确转义字符串中的引号。或者,如果您要将表达式转换为eval,请确保提供给您的是您愿意接受并使用python的有限语法子集。
- @欧文斯:"设计成一个字符串,而不是任意的代码。"那是无关的。这只是一个字符串值,因为它是一个字符串,所以您永远不会通过eval()。来自"外部世界"的代码不能被清除。来自外部世界的弦只是弦。我不清楚你在说什么。也许你应该在这里提供一个更完整的博客文章和链接。
- 总之,他们可以也不可以,这取决于你使用eval的目的。我不会花更多的时间讨论它。
- @欧文斯:"这取决于你用eval做什么。"?什么?你的评论一开始是在谈论来自"外部世界"(一个不可信的来源)的数据的评估,并被用作脚本攻击。显然,你不再谈论这个了?如果是这样的话,那么我就不知道你对埃瓦尔不喜欢什么。如果你说埃瓦尔不是完全邪恶的,那么我们似乎同意。
- @S.lott你知道你可以给eval传递字符串吗?eval接受代码对象或字符串,然后将其解析为python。如果您接受任何用户输入,并且任何输入都进入了被回避的字符串中,那么任意代码注入的可能性很大。运行由用户输入组成的代码听起来可能是个坏主意,而且应该这样做,但关键是不是每个人都会考虑到这一点。一些使用eval的人正在使用用户输入,他们甚至可能正在清理(或者认为他们正在清理)该输入,但可能忽略了一些可以插入代码的转义序列。
在这种情况下,是的。而不是
您应该使用内置函数setattr:
1
| setattr(self, 'Foo', val) |
是的,它是:
使用python进行黑客攻击:
1 2 3 4 5
| >>> eval(input())
"__import__('os').listdir('.')"
...........
........... #dir listing
........... |
下面的代码将列出在Windows计算机上运行的所有任务。
1 2
| >>> eval(input())
"__import__('subprocess').Popen(['tasklist'],stdout=__import__('subprocess').PIPE).communicate()[0]" |
在Linux:
1 2
| >>> eval(input())
"__import__('subprocess').Popen(['ps', 'aux'],stdout=__import__('subprocess').PIPE).communicate()[0]" |
值得注意的是,对于所讨论的具体问题,使用eval有几种替代方法:
如前所述,最简单的方法是使用setattr:
1 2 3
| def __init__(self):
for name in attsToStore:
setattr(self, name, None) |
一种不太明显的方法是直接更新对象的__dict__对象。如果您只想初始化None的属性,那么这就不那么简单了。但是考虑一下:
1 2 3
| def __init__(self, **kwargs):
for name in self.attsToStore:
self.__dict__[name] = kwargs.get(name, None) |
这允许您将关键字参数传递给构造函数,例如:
1
| s = Song(name='History', artist='The Verve') |
它还允许您更明确地使用locals(),例如:
…如果您真的想将None分配给在locals()中找到其名称的属性:
1
| s = Song(**dict([(k, None) for k in locals().keys()])) |
为对象提供属性列表的默认值的另一种方法是定义类的__getattr__方法:
1 2 3 4
| def __getattr__(self, name):
if name in self.attsToStore:
return None
raise NameError, name |
当未以正常方式找到命名属性时,将调用此方法。这种方法比简单地在构造函数中设置属性或更新__dict__要简单一些,但它的优点是,除非存在属性,否则实际上不会创建属性,这可以大大减少类的内存使用。
这一切的要点是:一般来说,有很多原因可以避免eval—执行不受控制的代码的安全问题,不能调试的代码的实际问题,等等。但更重要的原因是,一般来说,你不需要使用它。Python向程序员公开了它的许多内部机制,以至于几乎不需要编写编写代码的代码。
- 另一种可以说是或多或少的Python式的方法:不直接使用对象的__dict__,而是通过继承或属性给对象一个实际的字典对象。
- "不太明显的方法是直接更新对象的dict对象"=>请注意,这将绕过任何描述符(属性或其他)或__setattr__重写,这可能会导致意外的结果。setattr()没有这个问题。
其他用户指出了如何更改代码,使其不依赖于eval;我将为使用eval提供一个合法的用例,这个用例甚至可以在cpython中找到:测试。
下面是我在test_unary.py中发现的一个例子,其中一个关于(+|-|~)b'a'是否引发TypeError的测试:
1 2 3 4
| 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测试。
当使用eval()来处理用户提供的输入时,您可以让用户通过如下方式进入repl:
1
| "__import__('code').InteractiveConsole(locals=globals()).interact()" |
您可以摆脱它,但通常情况下,您不希望在应用程序中使用向量来执行任意代码。
除了@nadia alramli answer之外,由于我对python不太熟悉,并且很想了解使用eval会如何影响计时,我尝试了一个小程序,下面是观察结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #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 |