任务
Python 元组可以很方便地被用来将信息分组,但是访问每个子项都需要使用数字索引,所以这种用法有点不便。你希望能够创建一种可以通过名字属性访问的元组。
解决方案
工厂函数是生成符合要求的元组的子类的最简单方法:
#若在2.4中可使用operator.itemgetter,若在2.3中则需要实现itemgetter
try:from operator import itemgetter
except ImportError:def itemgetter(i):def getter(self):return self[i]return getterdef superTuple(typename,*attribute_names):"创建并返回拥有名字属性的元组子类"#给子类适合的__new__和__repr__特殊方法nargs = len(attribute_names)class supertup(tuple):#我们不需要每个实例提供一个字典,节省内存__slots__ = ()def __new__(cls,*args):if len(args) != narqs:raise TypeError,'%s takes exactly %d arquments(%d given)'%(typename,nargs,len(args))return tuple.__new__(cls,args)def __repr__(self):return '%s(%s)'%(typename,','.join(map(repr,self)))#给我们的元组子类添加一些键for index,attr_name in enumerate(attribute_names):setattr(supertup,attr_name,property(itemgetter(index)))supertup.__name__ = typenamereturn supertup
讨论
你常常需要用元组传递数据,就像C语言中的结构,或者其他语言中的简单记录一样。但你总是需要记住每个数字索引对应到哪个城并需要用数字索引来访问数据,这种用法令人心烦。一些 Python 标准库模块,比如 time 和 os,在旧版 Python 中总是返回元组,让这种烦恼更是雪上加霜。事实上,如果有一个类似于元组的类型,可以允许你通过名字访问值,就像通过属性名访问一样,同时也提供了数字索引的访问方式,那一定会是个很受欢迎的方案。本节展示了如何编程来实现这样的效果,本质上我们只需要自动地创建一个元组的定制子类。
要创建一个新的定制类型有很多方法,定制元类对于此类任务通常是最好的方式。但在本节中,一个简单的工厂函数足矣,而且你也应该避免使用超出需求的技术。下面展示怎样在代码中使用工厂函数superTuple,此处假设你已经将解决方案中的代码存为一个名为 supertuple.py的模块,并将它放入了你的Python的sys.path中:
>>> import supertuple
>>> Point = supertuple.superTuple('Point','x','y')
>>> Point
<class 'supertuple.Point'>
>>>p = Point(1,2,3)#故意给错误的数字
Traceback (most recent call last):File "",line 1 in?File "C:\Python24\Lib\site-packages\superTuple.py",line 16, in __new__ raise TypeError,'%s takes exactly %d arguments (%d given)' %
TypeError:Point takes exactly 2 arguments(3 given)
>>> p = Point(1,2)#这次给正确的数字
>>> p
Point(1,2)
>>> print p.x,p.y
1 2
函数 superTuple 的实现相当直接易懂。为了创建新的子类,superTuple 使用了一个 class声明,在声明的主体部分,它定义了三个特殊方法:一个“空”的__slots__(为了节省内存,我们的 supertuple 类并不需要为每个实例提供一个字典);一个__new__方法,这是为了在托管给 tuple.__new__之前先检查参数的个数;以及一个__repr__方法。当一个新的类对象被创建之后,我们为每个我们需要的命名属性设置了一个 property。每个这样的 property 都只是一个“getter”,这是因为我们的 supertuples 就像元组一样,是不可改变的——没有对值域进行设置的方法。最后,我们设置新类的名字并返回类对象。
通过简单地调用标准库模块 operator 的内建的 itemgetter 就可以创建这些 getter 方法。不过由于 operator.itemgetter 在 Python 2.4 中才被引入,在模块的开头我们需要确定用什么方式才能获得可用的itemgetter,即使是在Python2.3中,我们也可以提供自己的实现。