|
|
51CTO旗下网站
|
|
移动端

2.1.2 集合类型(2)

《Python高级编程(第2版)》第2章语法最佳实践——类级别以下,本章将介绍现在这门语言的语法中最重要的元素,以及它们的使用技巧。本节为大家介绍集合类型。

作者:张亮/阿信 译来源:人民邮电出版社|2018-01-29 18:19

2.1.2 集合类型(2)

2.字典

字典是Python中最通用的数据结构之一。dict可以将一组唯一键映射到对应的值,如下所示:

  1. {  
  2.     1: ' one',  
  3.     2: ' two',  
  4.     3: ' three',  

字典是你应该已经了解的基本内容。不管怎样,程序员还可以用和前面列表推导类似的推导来创建一个新的字典。这里有一个非常简单的例子如下所示:

  1. squares = {number: number**2 for number in range(100)} 

重要的是,使用字典推导具有与列表推导相同的优点。因此在许多情况下,字典推导要更加高效、更加简短、更加整洁。对于更复杂的代码而言,需要用到许多if语句或函数调用来创建一个字典,这时最好使用简单的for循环,尤其是它还提高了可读性。

对于刚刚接触Python 3的Python程序员来说,在遍历字典元素时有一点需要特别注意。字典的keys()、values()和items()3个方法的返回值类型不再是列表。此外,与之对应的iterkeys()、itervalues()和iteritems()本来返回的是迭代器,而Python 3中并没有这3个方法。现在keys()、values()和items()返回的是视图对象(view objects)。

keys():返回dict _ keys对象,可以查看字典的所有键。

values():返回dict _ values对象,可以查看字典的所有值。

it ems():返回dict _ items对象,可以查看字典所有的(key, value)二元元组。

视图对象可以动态查看字典的内容,因此每次字典发生变化时,视图都会相应改变,见下面这个例子:

  1. >>> words = {'foo': 'bar', 'fizz': 'bazz'}  
  2. >>> items = words.items()  
  3. >>> words['spam'] = 'eggs'  
  4. >>> items  
  5. dict_items([('spam', 'eggs'), ('fizz', 'bazz'), ('foo', 'bar')]) 

视图对象既有旧的keys()、values()和items()方法返回的列表的特性,也有旧的iterkeys()、itervalues()和iteritems()方法返回的迭代器的特性。视图无需冗余地将所有值都保存在内存里(像列表那样),但你仍然可以获取其长度(使用len),也可以测试元素是否包含其中(使用in子句)。当然,视图是可迭代的。

最后一件重要的事情是,在keys()和values()方法返回的视图中,键和值的顺序是完全对应的。在Python 2中,如果你想保证获取的键和值顺序一致,那么在两次函数调用之间不能修改字典的内容。现在dict _ keys和dict _ values是动态的,所以即使在调用keys()和values()之间字典内容发生了变化,那么这两个视图的元素遍历顺序也是完全一致的。

(1)实现细节
CPython使用伪随机探测(pseudo-random probing)的散列表(hash table)作为字典的底层数据结构。这似乎是非常高深的实现细节,但在短期内不太可能发生变化,所以程序员也可以把它当做一个有趣的事实来了解。

由于这一实现细节,只有可哈希的(hashable)对象才能作为字典的键。如果一个对象有一个在整个生命周期都不变的散列值(hash value),而且这个值可以与其他对象进行比较,那么这个对象就是可哈希的。Python所有不可变的内置类型都是可哈希的。可变类型(如列表、字典和集合)是不可哈希的,因此不能作为字典的键。定义可哈希类型的协议包括下面这两个方法。

__ hash __:这一方法给出dict内部实现需要的散列值(整数)。对于用户自定义类的实例对象,这个值由id()给出。

__ eq __:比较两个对象的值是否相等。对于用户自定义类,除了自身之外,所有实例对象默认不相等。
如果两个对象相等,那么它们的散列值一定相等。反之则不一定成立。这说明可能会发生散列冲突(hash collision),即散列值相等的两个对象可能并不相等。这是允许的,所有Python实现都必须解决散列冲突。CPython用开放定址法(open addressing)来解决这一冲突(https://en.wikipedia.org/wiki/Open_addressing)。不过,发生冲突的概率对性能有很大影响,如果概率很高,字典将无法从其内部优化中受益。

字典的3个基本操作(添加元素、获取元素和删除元素)的平均时间复杂度为O(1),但它们的平摊最坏情况复杂度要高得多,为O(n),这里的n是当前字典的元素数目。此外,如果字典的键是用户自定义类的对象,并且散列方法不正确的话(发生冲突的风险很大),那么这会给字典性能带来巨大的负面影响。CPython字典的时间复杂度的完整表格如表2-2所示。

表2-2

还有很重要的一点需要注意,在复制和遍历字典的操作中,最坏情况复杂度中的n是字典曾经达到的最大元素数目,而不是当前元素数目。换句话说,如果一个字典曾经元素个数很多,后来又大大减少了,那么遍历这个字典可能要花费相当长的时间。因此在某些情况下,如果需要频繁遍历某个字典,那么最好创建一个新的字典对象,而不是仅在旧字典中删除元素。

(2)缺点和替代方案

使用字典的常见陷阱之一,就是它并不会按照键的添加顺序来保存元素的顺序。在某些情况下,字典的键是连续的,对应的散列值也是连续值(例如整数),那么由于字典的内部实现,元素的顺序可能和添加顺序相同:

  1. >>> {number: None for number in range(5)}.keys()  
  2. dict_keys([0, 1, 2, 3, 4]) 

不过,如果使用散列方法不同的其他数据类型,那么字典就不会保存元素顺序。下面是CPython中的例子:

  1. >>> {str(number): None for number in range(5)}.keys()  
  2. dict_keys(['1', '2', '4', '0', '3'])  
  3. >>> {str(number): None for number in reversed(range(5))}.keys()  
  4. dict_keys(['2', '3', '1', '4', '0']) 

喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书频道活动讨论群:365934973

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

Linux编程技术详解

本书全面介绍了Linux编程相关的知识,内容涵盖Linux基本知识、如何建立Linux开发环境、Linux开发工具、Linux文件系统、文件I/O操作、设备文...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊