问题:为什么使用“评估”是一种不好的做法?
我正在使用以下类轻松存储我的歌曲的数据。
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
似乎被认为是不良做法,使用不安全。如果是这样,有人可以向我解释原因并向我展示定义上述类的更好方法吗?
回答 0
是的,使用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)
编辑:
在某些情况下,您必须使用eval或exec。但是它们很少见。当然,在您的情况下使用eval是一个不好的做法。我要强调不好的做法,因为eval和exec经常在错误的地方使用。
编辑2:
似乎有些不同意,在OP案件中,评估是“非常危险和不安全的”。对于这种特定情况,这可能是正确的,但一般而言并非如此。问题是一般性的,我列出的理由也适用于一般性情况。
编辑3: 重新排序的点1和4
回答 1
使用eval
是很弱的,不是一个明显的坏习惯。
它违反了“软件基本原理”。您的来源不是可执行文件的总和。除了您的资料来源外,还
eval
必须清楚地了解到的参数。因此,它是万不得已的工具。通常,这是经过漫长设计的标志。动态构建动态源代码的理由很少。委托和其他OO设计技术几乎可以完成任何事情。
这会导致相对缓慢的小代码即时编译。通过使用更好的设计模式可以避免开销。
作为注脚,在精神错乱的社会主义者的手中,这可能效果不佳。但是,当遇到精神错乱的用户或管理员时,最好不要首先让他们理解Python。在真正的邪恶之手,Python可以承担责任。eval
完全不会增加风险。
回答 2
回答 3
是的:
使用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]"
回答 4
值得注意的是,对于有问题的特定问题,可以使用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
如果无法以常规方式找到named属性,则调用此方法。这种方法比简单地在构造函数中设置属性或更新的方式要简单一些__dict__
,但是它的优点是除非存在该属性,否则不实际创建该属性,这样可以大大减少类的内存使用量。
所有这些的要点:通常有很多原因可以避免:避免eval
执行无法控制的代码的安全性问题,无法调试的代码的实际问题等。但是,更重要的原因是通常,您不需要使用它。Python向程序员公开了很多内部机制,因此您几乎不需要编写编写代码的代码。
回答 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
,在CPython的Git仓库中进行; 大量使用eval进行测试。
回答 6
什么时候 eval()
用于处理用户提供的输入时,您使用户能够拖放到提供以下内容:
"__import__('code').InteractiveConsole(locals=globals()).interact()"
您可以摆脱它,但是通常您不希望向量在您的应用程序中执行任意代码。
回答 7
除了@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