Python Cookbook-4.18 搜集命名的子项
任务
你想搜集一系列的子项,并命名这些子项,而且你认为用字典来实现有点不便。
解决方案
任意一个类的实例都继承了一个被封装到内部的字典,它用这个字典来记录自己的状态。我们可以很容易地利用这个被封装的字典达到目的,只需要写一个内容几乎为空的类:
class Bunch(object):
def __init__(self,**kwds):
self.__dict__.update(kwds)
现在,为了将变量组织起来,创建一个 Bunch 实例:
point = Bunch(datum = y,squared = y*y,coord = x)
现在就可以访问并重新绑定那些刚被创建的命名属性了,也可以进行添加、移除某些属性之类操作。比如:
if point.squared > threshold:
point.isok = True
讨论
我们常常需要搜集一些元素,然后给它们命名。这个需求用字典来实现完全没有问题但是利用一个几乎什么都不做的小类明显更加方便美观。
如同解决方案的代码所示,创建一个小小的类,提供参数访问的语法,我们几乎不用写什么东西。字典也适合用来搜集一些子项,每个子项都有自己的名字(根据环境,字典中的子项的键可以被认为是该子项的名字),但如果所有的名字都是标识符,而且被当做变量使用,字典并不是最好的方案。在类Bunch的__init__ 方法中,通过**kwds语法,可以接受任意的命名参数,并且用kwds参数来更新实例的空字典,这样,每个命名的参数都成为实例的一个属性。
与访问属性的语法相比,字典索引语法不是那么简洁和易读。比如,如果point是个字典,解决方案中的最后的那个代码片段就应该是这样:
if point['squared'] > threshold:
point['isok'] = True
此外,还有另一个备选的实现方案,看上去也很吸引人:
class EvenSimplerBunch(obiect):
def __init__(self,**kwds):
self.__dict__ = kwds
将实例的字典重新绑定可能会让人感到不安,但比起调用字典的 update 方法,它不会造成什么不好的后果。所以,你可能会喜欢这个备选的Bunch 实现在速度上的优势。不过,我从来没有在任何 Python 文档中看到对下面用法的担保:
d = {'foo':'bar'}
x = EvenSimplerBunch(**d)
最好使x.__dict__成为字典d的一个独立的拷贝,而不是共享一个引用。现在这个方法的确有效,在各个版本中都能工作,但除非语法文档规定了其语义,否则我们不能确信这种做法会永远有效。所以,如果选择了EvenSimplerBunch的实现,你可能会选择赋值一个拷贝(dict(kwds)或者 kwds.copy()),而不是kwds 本身。而且,如果这样做,那一点速度优势也就消失了。总之,最好还是将原先的Bunch的实现方法作为首选。
另一个富诱惑的做法是直接让 Bunch 类继承 dict,并将属性访问的特殊方法设为该子项本身的属性方法,像下面这样:
class DictBunch(dict):
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
这个方法的一个问题是,根据定义,DictBunch的一个实例x会拥有很多它实际上没有的属性,因为它获得了 dict所有的属性(实际上是方法,但在这个环境中其实没什么区别)。所以,你通过 hasattr(x,someattr)来检查属性没有意义,但可以对先前实现的Bunch 和EvenSimplerBunch 这么做,而且,你还得事先排除 someattr 的值是一些通用语如“keys”、“pop”和“get”等的可能性。
Pyihon 的关于属性和子项的区别是这门语言清晰和简洁的源泉。不幸的是,很多 Python新手错误地以为将属性和子项混为一谈并没有什么问题,这大概是因为先前的JavaScript 和其他语言的经验,在JavaScript 中,属性和条目通常是可以混为一谈的。不过新手应该把概念厘清,不要继续稀里糊涂。