|
|
|
|
移动端

2.1.2 集合类型(1)

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

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

技术沙龙 | 邀您于8月25日与国美/AWS/转转三位专家共同探讨小程序电商实战

2.1.2 集合类型(1)

Python提供了许多内置的数据集合类型,如果选择明智的话,可以高效解决许多问题。你可能已经学过下面这些集合类型,它们都有专门的字面值,如下所示。

列表(list)。

元组(tuple)。

字典(dictionary)。

集合(set)

Python的集合类型当然不止这4种,它的标准库扩展了其可选列表。在许多情况下,问题的答案可能正如选择正确的数据结构一样简单。本书的这一部分将深入介绍各种集合类型,以帮你做出更好的选择。

1.列表与元组

Python最基本的两个集合类型就是列表与元组,它们都表示对象序列。只要是花几小时学过Python的人,应该都很容易发现二者之间的根本区别:列表是动态的,其大小可以改变;而元组是不可变的,一旦创建就不能修改。

虽然快速分配/释放小型对象的优化方法有很多,但对于元素位置本身也是信息的数据结构来说,推荐使用元组这一数据类型。举个例子,想要保存(x, y)坐标对,元组可能是一个很好的选择。反正关于元组的细节相当无趣。本章关于元组唯一重要的内容就是,tuple是不可变的(immutable),因此也是可哈希的(hashable)。其具体含义将会在后面“字典”一节介绍。比元组更有趣的是另一种动态的数据结构list,以及它的工作原理和高效处理理方式。

(1)实现细节

许多程序员容易将Python的list类型与其他语言(如C、C++或Java)标准库中常见的链表的概念相混淆。事实上,CPython的列表根本不是列表。在CPython中,列表被实现为长度可变的数组。对于其他Python实现(如Jython和IronPython)而言,这种说法应该也是正确的,虽然这些项目的文档中没有记录其实现细节。造成这种混淆的原因很清楚。这种数据类型被命名为列表,还和链表实现有相似的接口。

为什么这一点很重要,这又意味着什么呢?列表是最常见的数据结构之一,其使用方式会对所有应用的性能带来极大影响。此外,CPython又是最常见也最常用的Python实现,所以了解其内部实现细节至关重要。

从细节上来看,Python中的列表是由对其他对象的引用组成的的连续数组。指向这个数组的指针及其长度被保存在一个列表头结构中。这意味着,每次添加或删除一个元素时,由引用组成的数组需要改变大小(重新分配)。幸运的是,Python在创建这些数组时采用了指数过分配(exponential over-allocation),所以并不是每次操作都需要改变数组大小。这也是添加或取出元素的平摊复杂度较低的原因。不幸的是,在普通链表中“代价很小”的其他一些操作在Python中的计算复杂度却相对较高:

利用list.insert方法在任意位置插入一个元素——复杂度为O(n)。

利用list.delete或del删除一个元素——复杂度为O(n)。
这里n是列表的长度。至少利用索引来查找或修改元素的时间开销与列表大小无关。表2-1是一张完整的表格,列出了大多数列表操作的平均时间复杂度。

表2-1

续表

对于需要真正的链表(或者简单来说,双端append和pop操作的复杂度都是O(1)的数据结构)的场景,Python在内置的collections模块中提供了deque(双端队列)。它是栈和队列的一般化,在需要用到双向链表的地方都可以使用这种数据结构。

(2)列表推导

你可能知道,编写这样的代码是很痛苦的:

  1. >>> evens = []  
  2. >>> for i in range(10):  
  3. ...     if i % 2 == 0:  
  4. ...         evens.append(i)  
  5. ...  
  6. >>> evens  
  7. [0, 2, 4, 6, 8] 

这种写法可能适用于C语言,但在Python中的实际运行速度很慢,原因如下。

解释器在每次循环中都需要判断序列中的哪一部分需要修改。

需要用一个计数器来跟踪需要处理的元素。

由于append()是一个列表方法,所以每次遍历时还需要额外执行一个查询函数。

列表推导正是解决这个问题的正确方法。它使用编排好的功能对上述语法的一部分做了自动化处理:

  1. >>> [i for i in range(10) if i % 2 == 0]  
  2. [0, 2, 4, 6, 8] 

这种写法除了更加高效之外,也更加简短,涉及的语法元素也更少。在大型程序中,这意味着更少的错误,代码也更容易阅读和理解。


列表推导和内部数组调整大小 

有些Python程序员中会谣传这样的说法:每添加几个元素之后都要对表示列表对象的内部数组大小进行调整,这个问题可以用列表推导来解决。还有人说一次分配就可以将数组大小调整到刚刚好。不幸的是,这些说法都是不正确的。

解释器在对列表推导进行求值的过程中并不知道最终结果容器的大小,也就无法为它预先分配数组的最终大小。因此,内部数组的重新分配方式与for循环中完全相同。但在许多情况下,与普通循环相比,使用列表推导创建列表要更加整洁、更加快速。

(3)其他习语

Python习语的另一个典型例子是使用enumerate(枚举)。在循环中使用序列时,这个内置函数可以很方便地获取其索引。以下面这段代码为例:

  1. >>> i = 0 
  2. >>> for element in ['one', 'two', 'three']:  
  3. ...     print(i, element)  
  4. ...     i += 1  
  5. ...  
  6. 0 one  
  7. 1 two  
  8. 2 three 

它可以替换为下面这段更短的代码:

  1. >>> for i, element in enumerate(['one', 'two', 'three']):  
  2. ...     print(i, element)  
  3. ...  
  4. 0 one  
  5. 1 two  
  6. 2 three 

如果需要一个一个合并多个列表(或任意可迭代对象)中的元素,那么可以使用内置的zip()函数。对两个大小相等的可迭代对象进行均匀遍历时,这是一种非常常用的模式:

  1. >>> for item in zip([1, 2, 3], [4, 5, 6]):  
  2. ...     print(item)  
  3. ...  
  4. (1, 4)  
  5. (2, 5)   
  6. (3, 6) 

注意,对zip()函数返回的结果再次调用zip(),可以将其恢复原状:

  1. >>> for item in zip(*zip([1, 2, 3], [4, 5, 6])):  
  2. ...     print(item)  
  3. ...  
  4. (1, 2, 3)  
  5. (4, 5, 6) 

另一个常用的语法元素是序列解包(sequence unpacking)。这种方法并不限于列表和元组,而是适用于任意序列类型(甚至包括字符串和字节序列)。只要赋值运算符左边的变量数目与序列中的元素数目相等,你都可以用这种方法将元素序列解包到另一组变量中:

  1. >>> first, second, third = "foo", "bar", 100  
  2. >>> first  
  3. 'foo'  
  4. >>> second  
  5. 'bar'  
  6. >>> third  
  7. 100 

解包还可以利用带星号的表达式获取单个变量中的多个元素,只要它的解释没有歧义即可。还可以对嵌套序列进行解包。特别是在遍历由序列构成的复杂数据结构时,这种方法非常实用。下面是一些更复杂的解包示例:

  1. >>> # 带星号的表达式可以获取序列的剩余部分  
  2. >>> first, second, *rest = 0, 1, 2, 3  
  3. >>> first  
  4. 0  
  5. >>> second  
  6. 1  
  7. >>> rest  
  8. [2, 3]  
  9.  
  10. >>> # 带星号的表达式可以获取序列的中间部分  
  11. >>> first, *inner, last = 0, 1, 2, 3  
  12. >>> first  
  13. 0  
  14. >>> inner  
  15. [1, 2]  
  16. >>> last  
  17. 3  
  18.  
  19. >>> # 嵌套解包  
  20. >>> (a, b), (c, d) = (1, 2), (3, 4)  
  21. >>> a, b, c, d  
  22. (1, 2, 3, 4) 

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

51CTO读书频道二维码


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

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

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

读 书 +更多

人月神话:32周年中文纪念版

在软件领域,很少能有像《人月神话》一样具有深远影响力并且畅销不衰的著作。Brooks博士为人们管理复杂项目提供了最具洞察力的见解。既有很...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊