当前位置: 首页 > news >正文

Python Cookbook-5.14 给字典类型增加排名功能

任务

你需要用字典存储一些键和“分数”的映射关系。你经常需要以自然顺序(即以分数的升序)访问键和分数值,并能够根据那个顺序检查一个键的排名。对这个问题,用dict 似乎不太合适。

解决方案

我们可以使用 dict 的子类,根据需要增加或者重写一些方法。在我们使用多继承、将UserDict.DictMixin 放置在基类 dict、并仔细安排各种方法的委托或重写之前,我们可以设法获得一种美妙的平衡,既拥有极好的性能又避免了编写一些冗余代码。

我们可以在文档字符串中加入很多示例,还可以用标准库的 doctest 模块来提供单元测试的功能,这也能够确保我们在文档字符串中编写的例子的准确性:

#!/usr/bin/env python
'''一个反映键到分数的映射的字典'''
from bisect import bisect_left,insort_left
import UserDict
class Ratings(UserDict.DictMixin,dict):
	'''Ratings类很像一个字典,但有一些额外特性:每个键的对应值都是该键的“分数”,所有键都根据它们的分数排名。对应值必须是可以比较的,同样,键则必须是可哈希的(即可以“绑”在分数上)
		所有关于映射的行为都如同预期一样,比如:
		>>>r = Ratings({"bob":30,"john":30})
		>>>len(r)
		2
		>>>r.has_key("paul"),"paul" in r
		(False,False)
		>>>r["john"] = 20
		r.update({"paul":20,"tom":10})
		>>>len(r)
		4
		>>>r.has_key("paul"),"paul" in r
		(True,True)
		>>>[r[key} for key in ["bob","paul","john","tom"]]
		[30,20,20,10]
		>>>r.get("nobody"),r.get("nobody",0)
		(None,0)
		除了映射的接口,我们还提供了和排名相关的方法。
		r.rating(key)返回了某个键的排名,其中排名为0的
		是最低的分数(如果两个键的分数相同,则直接比较
		它们两者,“打破僵局”,较小的键排名更低):
		>>>[r.rating(key) for key in ["bob","paul","john","tom"]]
		[3,2,1,0]
		getValueByRating(ranking)和getKeyByRating(ranking)
		对于给定的排名索引,分别返回分数和键:
		>>>[r.getValueByRating(rating) for rating in range(4)]
		[10,20,20,30]
		>>>[r.getKeyByRating(rating) for rating in range(4)]
		['tom','john','paul','bob']
		一个重要的特性是keys()返回的键是以排名的升序排列的,
		而其他所有返回的相关的列表或迭代器都遵循这个顺序:
		>>> r.keys()
		['tom','john','paul','bob']
		>>>[key for key in r]
		['tom','john','paul','bob']
		>>> [key for key in r.iterkeys()]
		['tom','john','paul','bob']
		>>> r.values()
		[10,20,20,30]
		>>>[value for value in r.itervalues()]
		[10,20,20,30]
		>>> r.items()
		[('tom',10),('john',20),('paul',20),('bob',30)]
		>>>[item for item in r.iteritems()]
		[('tom',10),('john',20),('paul',20),('bob',30)]
		实例可以被修改(添加、改变和删除键-分数对应关系)
		而且实例的每个方法都反映了实例的当前状态:
		>>>r["tom"] = 100
		>>> r.items()
		[('john',20),('paul',20),('bob',30),('tom',100)]
		>>>del r["paul"]
		>>>r.items()
		[('john',20),('bob',30),('tom',100)]
		>>>r["paul"] = 25
		>>>r.items()
		[('john',20),('paul',25),('bob',30),('tom',100)]
		>>>r.clear()
		>>>r.items()
		[  ]
		'''
	'''这个实现小心翼翼地混合了继承和托管,因此在尽量减少冗余代码的前提下获得了不错的性能,当然,同时也保证了语义的正确性。所有未被实现的映射方法都通过继承来获得,大多来自DictMixin,但关键的__getitem__来自 dict。'''
	def init(self,*args,**kwds):
		'''这个类就像dict一样被实例化'''
		dict.__init__(self,*args,**kwds)
		#self._rating是关键的辅助数据结构:一个所有(值,键)
		#的列表,并保有一种“自然的”排序状态
		self._rating =[ (v,k) for k,v in dict.iteritems(self)]
		self._rating.sort()
	def copy(self):
		'''提供一个完全相同但独立的拷贝'''
		return Ratings(self)
	def __setitem__(self,k,y):
		'''除了把主要任务委托给dict,我们还维护self._rating'''
		if k in self:
			del self._rating[self.rating(k)]
		dict.__setitem__(self,k,v)
		insort_left(self._rating,(v,k))
	def __delitem__(self,k):
		'''除了把主要任务委托给dict,我们还维护self._rating'''
		del self._rating[self.rating(k)]
		dict.__delitem__(self,k)
	'''显式地将某些方法委托给dict的对应方法,以免继承了DictMixin的较慢的(虽然功能正确)实现'''
	__len__ = dict.__len__
	__contains__ = dict.__contains__
	has_key = __contains__
	'''在self._rating和self.keys()之间的关键的语义联系————DictMixin“免费”给了我们所有其他方法,虽然我们直接实现它们能够获得稍好一点的性能。'''
	def __iter__(self):
		for v,k in self._rating:
			yield k
	iterkeys = __iter__
	def keys(self):
		return list(self)
		'''三个和排名相关的方法'''
	def rating(self,key):
		item = self[key],key
		i = bisect_left(self._rating,item)
		if item == self._rating[i]:
			return i
		raise LookupError,"item not found in rating"
	def getValueByRating(self,rating):
		return self._rating[rating][0]
	def getKeyByRating(self,rating):
		return self.rating[rating][1]
def _test():
	'''我们使用doctest来测试这个模块,模块名必须为rating.py,这样docstring中的示例才会有效'''
	import doctest,rating
	doctest.testmod(rating)
if __name__ == "__main__":
	_test()

讨论

在很多方面,字典都是很自然地被应用于存储键(比如,竞赛中参与者的名字)和“分数”(比如参与者获得的分数,或者参与者在拍卖中的出价)的对应关系的数据结构。如果我们希望在这些应用中使用字典,我们可能会希望以自然的顺序访间–即键对应的“分数”的升序——我们也希望能够迅速获得基于当前分数的排名(比如,参与者现在排在第三位,排在第二位的参与者的分数,等等)。

为了达到这个目的,本节给dict的子类增加了一些它本身完全不具备的功能(rating方法、getValueByRating、getKeyByRating),同时,最关键和巧妙的地方是,我们修改了keys方法和其他相关的方法,这样它们就能返回按照指定顺序排列的列表或者可选代对象(比如按照分数的升序排列,对于两个有同样分数的键,我们继续比较键本身)。大多数的文档都放在类的文档字符串中——保留文档和示例是很重要的,可以用Python 标准库的 doctest模块来提供单元测试的功能,以确保给出的例子是正确的。

关于这个实现的有趣之处是,它很关心消除冗余(即那些重复和令人厌烦的代码,很可能滋生 bug),但同时没有损害性能。Ratings 类同时从 dict 和 DictMixin 继承,并把后者排在基类列表的第一位,因此,除非明确地覆盖了基类的方法,Ratings 的方法基本来自于 DictMixin,如果它提供了的话。

Raymond Hettinger 的 DictMixin 类最初是发布在 Python Cookbook 在线版本中的一个例子,后来被吸收到了 Python2.3的标准库中。DictMixin 提供了各种映射的方法,除了__init__
、copy以及四个基本方法:__getitem__、__setitem__、__delitem__和 keys。如果需要的是一个映射类并且想要支持完整映射所具有的各种方法,可以从DictMixin派生子类,并且提供那些基本的方法(具体依赖于你的类的语义————比如,如果你的类有不可修改的实例,你无须提供属性设置方法__setitem__和__delitem__)。还可以添加一些可选的方法以提升性能,覆盖 DictMixin 所提供的原有方法。整个 DictMixin 的架构可以被看做是一个经典的模板方法设计模式(Template Method Design Pattern),它用一种混合的变体提供了广泛的适用性。

在本节的类中,从基类继承了__getitem__(准确地说,是从内建的dict类型继承),出于性能上的考虑,我们把能委托的都委托给了dict。我们必须自己实现基本的属性设置方法(__setitem__和__delitem__),因为除了委托给基类的方法,还需要维护一个数据结构 self._rating——这是一个列表,包含了许多(score,key)值对,此列表在标准库模块 bisect 的帮助下完成了排序。我们也重新实现了keys(在这个步骤中,还重新实现了__iter__,即 iterkeys,很明显,借助__iter__可以更容易地实现 keys)来利用self._rating 并按照我们需要的顺序返回键。最后,除了上面三个和排名有关的方法,我们又为__init__和 copy 添加了实现。

这个结果是一个很有趣的例子,它取得了简洁和清晰的平衡,并最大化地重用了 Python标准库的众多功能。如果你在应用程序中使用这个模块,测试结果可能会显示,本节的类从 DictMixin 继承来的方法的性能不是太让人满意,毕竞 DictMixin 的实现是基于必要的通用性的考虑。如果它的性能不能满足你的要求,可以自己提供一个实现来获取最高性能。假设有个Ratings类的实例r,你的应用程序需要对r.iteritems()的结果进行大量的循环处理,可以给类的主体部分增加这个方法的实现以获得更好的性能:

def iteritems(self):
	for v,k in self._rating:
		yield k,v

相关文章:

  • CSPM认证对项目论证的范式革新:从合规审查到价值创造的战略跃迁
  • MicroPython 开发ESP32应用教程 之 I2S、INMP441音频录制、MAX98357A音频播放、SD卡读写
  • 5Why分析法
  • 低压电工怎么备考,刷题题库分享
  • MySQL Slow Log
  • 三维凹多面体分解为凸多面体的MATLAB实现
  • 理解大模型论文中的名词部分
  • 深入剖析Go Channel:从底层原理到高阶避坑指南|Go语言进阶(5)
  • Next.js 平行路由详解
  • Linux系统中使用node -v出现GLIBC_2.27 not found问题的解决方案
  • 前端大屏可视化项目 局部全屏(指定盒子全屏)
  • 《算法笔记》3.5小节——入门模拟->进制转换
  • Halo 设置 GitHub - OAuth2 认证指南
  • 【模拟电路】达林顿管
  • Linux--进程信号
  • windows安装docker随记
  • 【Git】--- 企业级开发流程
  • MacOS下下载和编译webrtc步骤
  • AI Agent入门指南
  • 使用Golang打包jar应用
  • 前端只是做网站吗/全网营销软件
  • 在线名片制作网站开发/seo优化seo外包
  • 深圳有做网站最近价格/免费的网站申请
  • 外贸网站建设流程/企业网站seo案例
  • 长沙正规企业网站制作平台/搜索引擎营销的模式有哪些
  • 58同城给做网站/互联网精准营销