我正在使用以下课程轻松存储我的歌曲数据.
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
似乎被认为是一种不良做法并且使用起来不安全.如果是这样,任何人都可以向我解释为什么并告诉我一个更好的方法来定义上面的类?
是的,使用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:
看起来有些人不同意eval在OP情况下"非常危险且不安全".对于这个特定情况可能也是如此,但一般情况下并非如此.问题是一般性的,我列出的原因也适用于一般情况.
编辑3: 重新排序第1点和第4点
使用eval
很弱,不是一个明显不好的做法.
它违反了"软件基本原理".您的来源不是可执行文件的总和.除了你的来源之外,还有eval
必须清楚理解的论据.因此,它是最后的工具.
这通常是轻率设计的标志.动态源代码很少有充分的理由,即时构建.使用委托和其他OO设计技术几乎可以做任何事情.
它导致相对较慢的动态编译小块代码.通过使用更好的设计模式可以避免开销.
作为一个脚注,在疯狂的反社会手中,它可能不会很好.然而,当面对精神错乱的反社会用户或管理员时,最好不要首先给他们解释Python.在真正邪恶的手中,Python可以承担责任; eval
根本不会增加风险.
在这种情况下,是的.代替
exec 'self.Foo=val'
你应该使用内置函数setattr
:
setattr(self, 'Foo', val)
是的:
使用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]"
值得注意的是,对于所讨论的具体问题,有几种替代方法可供使用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向程序员公开了很多内部机制,你很少需要编写编写代码的代码.
其他用户指出如何更改代码以使其不依赖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
很方便测试.
看看这个搜索在为eval
,在CPython的Git仓库中进行; 使用eval进行测试的用量很大.