第2章 序列构成的数据

2.1 内置序列类

  1. 容器序列
    list、tuple 和collections.deque(双端的队列) 这些序列能存放不同类型的数据
  2. 扁平序列
    str、bytes、bytearray、memoryview 和array.array,这类序列只能容纳一种类型

容器序列存放的是它们所包含的任意类型的对象的引用,而扁平序列里存放的是值而不是
引用。换句话说,扁平序列其实是一段连续的内存空间。由此可见扁平序列其实更加紧
凑,但是它里面只能存放诸如字符、字节和数值这种基础类型

如果需要创建自己的序列类型, 需要在自定义的类中实现, getitemlen 方法, 来实现自定义类的可迭代的方式

按照能否修改来分类
1. 可变序列 (MutableSequence)
list、bytearray、array.array、collections.deque 和memoryview
2. 不可变序列 (Sequence)
tuple、str 和bytes。

可变序列中, 使用了 setitem 方法, 不可变的序列中, 只是实现了 getitem方法

2.2 列表推导式和生成器表达式

只可采用列表推导式, 来实现创建新的列表, 并且要尽量的保持简短

对比这map/filter函数, 列表推导式的速度会更加快一点

  1. 使用map和filter的示例:
beyond_ascii2 = list(filter(lambda c : c > 127, map(ord,symbols)))

beyond_ascii2
Out[9]: [162, 163, 165, 8364, 164]

beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]

beyond_ascii
Out[11]: [162, 163, 165, 8364, 164]
  1. 列表推导式实现, 负载的数据的类型
colors = ['black', 'whiter']

sizes = ['s', 'm', 'l'] 

tshirts = [(color, size) for color in colors for size in sizes]

tshirts
Out[15]:   # 返回的是一个列表中嵌套的元组
[('black', 's'),
 ('black', 'm'),
 ('black', 'l'),
 ('whiter', 's'),
 ('whiter', 'm'),
 ('whiter', 'l')]
  1. 实现字典推导式 就是把列表的形式, 更换成字典的形式
tshirts = {color: size for color in colors for size in sizes}
# 实现字典推导式, 需要把列表推导式的方法更换成字典的形式
tshirts
Out[23]: {'black': 'l', 'whiter': 'l'}

2.2.4 生成器表达式

可以用列表推导来初始化元组、数组或其他序列类型,但是生成器表达式是更好的
选择。这是因为生成器表达式背后遵守了迭代器协议,可以逐个地产出元素,而不是先建
立一个完整的列表,然后再把这个列表传递到某个构造函数里。生成器表达式的方式显然能够节
省内存。

用生成器表达式实现元组的初始化的操作

a = (ord(s) for s in symbols)

a
Out[30]:  at 0x0000000009D74258>

next(a)  # 使用next() 来逐渐的迭代出生成器中的元素, 采用的是算法的方式, 逐一的进行计算的操作, 节省内存
Out[31]: 36

next(a)
Out[32]: 162



tuple(a) # 使用tuple(a)  直接把生成器表达式, 转换成元组的形式
Out[35]: (165, 8364, 164)

元组不仅仅是不可变的列表

元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个
字段的位置。正是这个位置信息给数据赋予了意义

如果只把元组理解为不可变的列表,那其他信息——它所含有的元素的总数和它们的位
置——似乎就变得可有可无。但是如果把元组当作一些字段的集合,那么数量和位置信息
就变得非常重要了

元组的拆包形式:

优雅的不使用中间变量, 来交换两个变量的值, 就是使用了元组的拆包的形式b, a = a, b

还可以使用 * 运算符把一个可迭代的对象, 当作函数的参数, 传递给函数进行使用
, 在函数中使用的的变量, 就是一个元组了

def get_demo(*a):
    print(a)  # 打印出来的是一个元组的形式
    c, d = a  # 通过在函数的参数中使用 * 那么在函数中的 a 就成为了一个元组了
    print(c)  # 使用元组的拆包的形式
    print(d)
    return c, d  # 返回的时候, 同时返回多个参数的话, 返回的就是一个元组的形式

get_demo(*c)
(20, 8)
20
8
Out[43]: (20, 8)


当使用的是字典的形式的话 传入的字典的 键  key 必须是 字符串形式的
def get_dict(**kwargs):
    print(kwargs)
    for key, value in kwargs.items():
        print(key,value)

dict001 = {"1":2, "3":4}  # 外面构建的字典的键  必须是要  字符串形式的  因为在使用**kwargs的时候, 其实是指定的键和value的形式的
get_dict(**dict001)

使用os 来获取路径中的文件后缀, 和对路径进行切割

os.path.split("/usr/local/nginx.conf") # 使用split  获取的文件的名称
Out[59]: ('/usr/local', 'nginx.conf')

os.path.splitext("/usr/local/nginx.conf")  # splitxt 获取的是文件的后缀
Out[60]: ('/usr/local/nginx', '.conf')


可以使用  _ 来代替不需要的值, 这样就不用在去定义一个变量, 来接收不需要的key值
_, demo = os.path.split("/usr/local/nginx.conf")

在其他情况下,_ 会是一个很好
的占位符。

在python3 中的平行赋值的操作

在平行赋值的时候, *制定的变量可以出现的任何的位置, 但是还是会优先匹配指定的变量的数据, 后才会去匹配*变量
>>> a, b, *rest = range(5)
>>> a, b, rest
(0, 1, [2, 3, 4])
>>> a, b, *rest = range(3)
>>> a, b, rest
(0, 1, [2])
>>> a, b, *rest = range(2)
>>> a, b, rest
(0, 1, [])

2.3.4 使用具名元组

collections.namedtuple 是一个工厂函数,它可以用来构建一个带字段名的元组和一个有
名字的类——这个带名字的类对调试程序有很大帮助

用namedtuple 构建的类的实例所消耗的内存跟元组是一样的,因为字段名都
被存在对应的类里面。这个实例跟普通的对象实例比起来也要小一些,因为
Python 不会用__dict__ 来存放这些实例的属性
>>> from collections import namedtuple
>>> City = namedtuple('City', 'name country population coordinates')  # 构建一个类, 里面的 city相当于是一个类名的形式
>>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
>>> tokyo
City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722,
139.691667))
>>> tokyo.population
36.933
>>> tokyo.coordinates
(35.689722, 139.691667)
>>> tokyo[1]
'JP'

创建一个具名元组需要两个参数,一个是类名,另一个是类的各个字段的名字。后者可
以是由数个字符串组成的可迭代对象,或者是由空格分隔开的字段名组成的字符串

具名形式的元组具有的的属性和方法;

:_fields 类属性、类方法_make(iterable) 和实例方法_asdict()

>>> City._fields # 返回定义的字段的名称
('name', 'country', 'population', 'coordinates')
>>> LatLong = namedtuple('LatLong', 'lat long')
>>> delhi_data = ('Delhi NCR', 'IN', 21.935, LatLong(28.613889, 77.208889))
>>> delhi = City._make(delhi_data)  # 通过接受一个可迭代对象来生成这个类的一个实例
>>> delhi._asdict()  # 返回的是一个有序的字典的形式, 
OrderedDict([('name', 'Delhi NCR'), ('country', 'IN'), ('population',
21.935), ('coordinates', LatLong(lat=28.613889, long=77.208889))])

具名元组, 可以被理解成一个只有属性, 没有方法的简单的类的形式, 具名变量, 可以很好的替代这中形式的类

2.3.5 作为不可变列表的元组

元组没有__reversed__ 方法, 但是任然是可以使用 reverse(tuple) 的方法的

a = reversed(tuple001)
for item in a:
    print(item)

3.4
1

元组具有的方法:

s.__add__(s2) • s + s2,拼接成一个新的元组


a = (1,2)

b = (3,4)

a + b
Out[78]: (1, 2, 3, 4)

s.__contains__(e) • • s 是否包含e

s.count(e) • • e 在s 中出现的次数

s.__getitem__(p) • • s[p],获取位置p 的元素

s.index(e) • • 在s 中找到元素e 第一次出现的位置

s.__iter__() • • 获取s 的迭代器

s.__len__() • • len(s),元素的数量

s.__mul__(n) • • s * n,n 个s 的重复拼接

s.__rmul__(n) • • n * s,反向拼接*

2.4切片

使用切片的时候, 当切片的发超出前后的索引的范围的时候, 会自动的截止到前后的范围的

在Python 里,像列表(list)、元组(tuple)和字符串(str)这类序列类型都支持切片
操作

2.4.1 为什么切片和区间会忽略最后一个元素

Python、C 和其他语言里以0 作为起始下标的传统. 这样的好处是:
1. 当只有最后一个位置信息时,我们也可以快速看出切片和区间里有几个元素:range(3)和my_list[:3] 都返回3 个元素。
2. 当起止位置信息都可见时,我们可以快速计算出切片和区间的长度,用后一个数减去第
一个下标(stop - start)即可.
3. 这样做也让我们可以利用任意一个下标来把序列分割成不重叠的两部分,只要写成my_
list[:x] 和my_list[x:] 就可以了.

2.4.4 给切片赋值

如果把切片放在赋值语句的左边,或把它作为del 操作的对象,我们就可以对序列进行嫁
接、切除或就地修改操作

>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5] = [20, 30]
>>> l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
>>> del l[5:7]
>>> l
[0, 1, 20, 30, 5, 8, 9]
>>> l[3::2] = [11, 22]
>>> l
[0, 1, 20, 11, 5, 22, 9]
>>> l[2:5] = 100 ➊
Traceback (most recent call last):
File "", line 1, in 
TypeError: can only assign an iterable
>>> l[2:5] = [100]
>>> l
[0, 1, 100, 22, 9

如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代对象。即便只有单独
一个值,也要把它转换成可迭代的序列

2.5 对序列使用 + 和 *

Python 程序员会默认序列是支持+ 和* 操作的。通常+ 号两侧的序列由相同类型的数据所
构成,在拼接的过程中,两个被操作的序列都不会被修改,Python 会新建一个包含同样类
型数据的序列来作为拼接的结果

如果想要把一个序列复制几份然后再拼接起来,更快捷的做法是把这个序列乘以一个整
数。同样,这个操作会产生一个新序列

  • +和* 都遵循这个规律,不修改原有的操作对象,而是构建一个全新的序列。

  • 如果在a * n 这个语句中,序列a 里的元素是对其他可变对象的引用的话,
    你就需要格外注意了,因为这个式子的结果可能会出乎意料。比如,你想用
    my_list = [[]] * 3 来初始化一个由列表组成的列表,但是你得到的列表里
    包含的3 个元素其实是3 个引用,而且这3 个引用指向的都是同一个列表。
    这可能不是你想要的效果
my_list = [[]]*3

my_list
Out[2]: [[], [], []]

my_list[0].append("hehe")

my_list  # 是对同一个地址的引用, 这样改变其中的一个列表中的元素, 都会产生影响
Out[4]: [['hehe'], ['hehe'], ['hehe']]

2.5.1建立由列表组成的列表

有时我们会需要初始化一个嵌套着几个列表的列表,譬如一个列表可能需要用来存放不同
的学生名单,或者是一个井字游戏板3 上的一行方块。想要达成这些目的,最好的选择是使
用列表推导

示例1:

>>> board = [['_'] * 3 for i in range(3)]  # 列表中的元素是普通的变量, 建立的是三个不同的列表对象
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][2] = 'X'
>>> board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]  # 添加后互相不影响

上面的示例的本质是: 相当于 每次都创建一个新的对象加入的列表中

>>> board = []
>>> for i in range(3):
    row=['_'] * 3  #每次都创建一个新的对象添加到列表中
    board.append(row)
...
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[2][0] = 'X'
>>> board # ➋
[['_', '_', '_'], ['_', '_', '_'], ['X', '_', '_']]

示例2:

>>> weird_board = [['_'] * 3] * 3  # 列表中的元素是一个可变的对象
>>> weird_board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> weird_board[1][2] = 'O'
>>> weird_board
[['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]  # 修改任何的一个, 都会对其他的产生影响,

上面示例的本质是想一个列表中追加了,同一个对象导致的

row=['_'] * 3
board = []
for i in range(3):
    board.append(row)

2.6 序列的增量赋值

增量赋值运算符+= 和*= 的表现取决于它们的第一个操作对象。简单起见,我们把讨论集
中在增量加法(+=)上,但是这些概念对*= 和其他增量运算符来说都是一样的。
+= 背后的特殊方法是__iadd__(用于“就地加法”), 还是原来的变量。但是如果一个类没有实现这个方法的
话,Python 会退一步调用__add__, 创建新的变量。考虑下面这个简单的表达式

>>> a += b

如果a 实现了__iadd__ 方法, 就会调用这个方法。同时对可变序列来说,a 会就地改动,就像调用了a.extend(b) 一样。但是如果a 没有实现__iadd__ 的话,a += b 这个表达式的效果就变得跟a = a + b 一样了:首先
计算a + b,得到一个新的对象,然后赋值给a。也就是说,在这个表达式中,**变量名会不
会被关联到新的对象,完全取决于这个类型有没有实**现__iadd__ 这个方法。
总体来讲,可变序列一般都实现了__iadd__ 方法,因此+= 是就地加法。而不可变序列根
本就不支持这个操作,对这个方法的实现也就无从谈起

示例1:

>>> t = (1, 2, [30, 40])
>>> t[2] += [50, 60]

会出现的结果:
1. a. t 变成(1, 2, [30, 40, 50, 60])。
2. b. 因为tuple 不支持对它的元素赋值,所以会抛出TypeError 异常。
3. c. 以上两个都不是。
4. d. a 和b 都是对的。
答案是 d

>>> t = (1, 2, [30, 40])
>>> t[2] += [50, 60]
Traceback (most recent call last):
File "", line 1, in 
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])

• 不要把可变对象放在元组里面。
• 增量赋值不是一个原子操作。我们刚才也看到了,它虽然抛出了异常,但还是完成了操作

2.7 list.sort方法和内置函数sorted

list.sort 方法会就地排序列表,也就是说不会把原列表复制一份。这也是这个方法的返
回值是None 的原因,提醒你本方法不会新建一个列表。在这种情况下返回None 其实是
Python 的一个惯例:如果一个函数或者方法对对象进行的是就地改动,那它就应该返回
None,好让调用者知道传入的参数发生了变动,而且并未产生新的对象。例如,random.shuffle 函数也遵守了这个惯例

a = [1,2,3,4,5]

import random

b = random.shuffle(a)  # 打乱a的次序

a  # a的次序发生了变化
Out[8]: [1, 2, 4, 3, 5]

b  # b并没有值

list.sort 相反的是内置函数sorted,它会新建一个列表作为返回值。这个方法可以接
受任何形式的可迭代对象作为参数,甚至包括不可变序列或生成器。而不管
sorted 接受的是怎样的参数,它最后都会返回一个列表。

示例:

1. 使用有序序列自带的sort() 方法进行排序
list_temp = ["a", "B", "A", "x", "e", "f"]

list_temp.sort(key = str.lower)

list_temp
Out[3]: ['a', 'A', 'B', 'e', 'f', 'x']
# 直接对原来的列表进行排序, 修改了原来列表中元素的位置


2. 使用sorted 函数进行排序
list_temp002 = ["a", "B", "A", "x", "e", "f"]
sorted(list_temp002, key=str.lower)
Out[7]: ['a', 'A', 'B', 'e', 'f', 'x']

list_temp002
Out[8]: ['a', 'B', 'A', 'x', 'e', 'f']
# 排序后会生成一个新的列表, 不影响原来的列表中的元素

不管是list.sort 方法还是sorted 函数,都有两个可选的关键字参数
1. reverse # 如果被设定为true 会进行降序的排序, 默认的是升序的排序
2. key # 一个只有一个参数的函数,这个函数会被用在序列里的每一个元素上,所产生的结果
将是排序算法依赖的对比关键字

2.8 使用bisect来管理已经排序的序列

bisect 模块包含两个主要函数,bisectinsort,两个函数都利用二分查找算法来在有序
序列中查找或插入元素

2.8.1. 使用bisect 来进行搜索

bisect(haystack, needle)haystack(干草垛)里搜索needle(针)的位置,该位置满
足的条件是,把needle 插入这个位置之后haystack 还能保持升序。也就是在说这个函
数返回的位置前面的值,都小于或等于needle 的值。其中haystack 必须是一个有序的序
列。你可以先用bisect(haystack, needle) 查找位置index,再用haystack.insert(index,needle) 来插入新值。但你也可用insort 来一步到位,并且后者(insort)的速度更快一些

给予二分查找的方法来进行查找需要插入的元素的位置, 返回的是需要插入的位置
  1. bisect 查找元素位置的源码
def bisect_right(a, x, lo=0, hi=None):
    """Return the index where to insert item x in list a, assuming a is sorted.

    The return value i is such that all e in a[:i] have e <= x, and all e in
    a[i:] have e > x.  So if x already appears in the list, a.insert(x) will
    insert just after the rightmost x already there.

    Optional args lo (default 0) and hi (default len(a)) bound the
    slice of a to be searched.
    """

    if lo < 0:
        raise ValueError('lo must be non-negative')
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        if x < a[mid]: hi = mid
        else: lo = mid+1
    return lo

基本的使用方法:

示例1:

在有序序列中用bisect 查找某个元素的插入位置

依据的就是 采用的是二分查找的方法 默认的 最小的是0 最大的是 len-1
返回的是一个应该插入的位置

import bisect

HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30]

bisect.bisect(HAYSTACK, 10)  # 返回的下标是从1开始的 不是从0开始的
Out[12]: 5

bisect.bisect(HAYSTACK, 20)
Out[13]: 8

bisect.bisect_left(HAYSTACK, 10)  # 插入值得前面
Out[14]: 5

bisect.bisect_right(HAYSTACK, 10) # 插入值的后面
Out[15]: 5

bisect 函数其实是bisect_right 函数的别名,后者还有个姊妹函数叫bisect_left
它们的区别在于,bisect_left 返回的插入位置是原序列中跟被插入元素相等的元素的位置,
也就是新元素会被放置于它相等的元素的前面,而bisect_right 返回的则是跟它相等的元素
之后的位置

示例2:

根据一个分数, 找出所对应的成绩
import bisect

def grade(sorce, breakpoints=[60, 70, 80, 90], grades="FDCBA"):
    i = bisect.bisect(breakpoints, sorce)
    return grades[i]

grades_list = [grade(sorce) for sorce in [33, 99, 77, 70, 89, 90, 100]]
print(grades_list)

2.8.2 使用bisect.insort 来进行插入元素

排序很耗时,因此在得到一个有序序列之后,我们最好能够保持它的有序。bisect.insort
就是为了这个而存在的。
bisect.insort(seq, item) 把变量item 插入到序列seq 中,并能保持seq 的升序顺序
bisect.bisect() 可以检索出需要插入元素的位置

使用方法:

list_temp = [1,2,3,4,5,6]

bisect.insort(list_temp, 4)

list_temp # 直接的插入到了原来的有序的列表中了.
Out[21]: [1, 2, 3, 4, 4, 5, 6]

HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30]

def bisect_demo2():
    """使用bisect搜素目标值应插入的位置和进行插入的操作"""
    position = bisect.bisect(HAYSTACK, 2)
    print('应该插入的位置的索引是{}'.format(position))  # 返回的是 1 就是需要插入的位置是1
    # 使用bisect.insort  进行按照顺序进行插入的操作
    bisect.insort(HAYSTACK, 2)  # 把2 插入到序列中,并保持原有的顺序
    print(HAYSTACK)

if __name__ == '__main__':
    bisect_demo2()

2.9使用列表之外的数据结构

虽然列表既灵活又简单,但面对各类需求时,我们可能会有更好的选择。比如,要存放
1000 万个浮点数的话,数组(array)的效率要高得多,**因为数组在背后存的并不是float
对象,而是数字的机器翻译,也就是字节表述**。这一点就跟C 语言中的数组一样。再比如
说,如果需要频繁对序列做先进先出的操作,deque(双端队列)的速度应该会更快。

如果在你的代码里,包含操作(比如检查一个元素是否出现在一个集合中)的频率很高,用set(集合)会更合适。set 专为检查元素是否存在做过优化。但是它并不是序列,因为set 是无序的。

2.9.1 使用数组 array

如果我们需要一个只包含数字的列表,那么array.arraylist 更高效。数组支持所有跟
可变序列有关的操作,包括.pop、.insert 和.extend。另外,数组还提供从文件读取和存
入文件的更快的方法,如.frombytes 和.tofile

Python 数组跟C 语言数组一样精简。创建数组需要一个类型码,这个类型码用来表示在
底层的C 语言应该存放怎样的数据类型。比如b 类型码代表的是有符号的字符(signedchar),因此array('b') 创建出的数组就只能存放一个字节大小的整数,范围从-128 到127,这样在序列很大的时候,我们能节省很多空间。而且Python ==不会允许你在数组里存
放除指定类型之外的数据==。

示例:

使用arrar.fromfile 和 tofile 速度是很快的

from array import array
from random import random

floats = array('d', (random() for i in range(10**7)))  #   创建一个100万的双精度的浮点数
print(floats[-1])
fp = open('floats.txt', 'wb')
print(type(fp))  # 得到的类型是一个     内存中的缓冲写io
floats.tofile(fp)  # 把100万的浮点数  数组  写入到文件中
fp.close()
floats2 = array('d')  # 创建一个双精度的数组
fp = open('floats.txt', 'rb')
floats2.fromfile(fp, 10**7)  # 把数组从文件中读取出来
fp.close()
print(floats2[-1])
print(floats == floats2)

用array.fromfile 从一个二进制文件里读出1000 万个双精度浮点数只需要0.1 秒,这比从文本文件里读取的速度要快60 倍,因为后者会使用内置的float 方法把每一行文字转换成浮点数。另外,使用array.tofile 写入到二进制文件,比以每行一个浮点数的方式把所有数字写入到文本文件要快7倍。另外,1000 万个这样的数在二进制文件里只占用80 000 000 个字节(每个浮点数占用8 个字节,不需要任何额外空间),如果是文本文件的话,我们需要181 515 739 个字节

另外一个快速序列化数字类型的方法是使用pickle模块。pickle.dump 处理浮点数组的速度几乎跟array.tofile 一样快。不过前者可以处理几乎所有的内置数字类型,包含复数、嵌套集合,甚至用户自定义的类

2.9.2 内存试图

memoryview 是一个内置类,它能让用户在不复制内容的情况下操作同一个数组的不同切

2.9.2使用 NumPy 来操作高阶的的数组

对numpy.ndarray 的行和列进行基本操作

>>> import numpy
>>> a = numpy.arange(12) # 生成一个长度是12 的数组
>>> a
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> type(a)
'numpy.ndarray'>
>>> a.shape  # 查看数组的长度
(12,)
>>> a.shape = 3, 4  # 对数组进行转换, 转换成 34列  就相当于是 一个列表中嵌套的还是列表的操作
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> a[2]  # 取出array数组中的 第二个元素的数据
array([ 8, 9, 10, 11])
>>> a[2, 1]  # 获取 第二行第一列的数据, 下标是从0 开始的
9
>>> a[:, 1]
array([1, 5, 9])
>>> a.transpose()  # 反转数组
array([[ 0, 4, 8],
[ 1, 5, 9],
[ 2, 6, 10],
[ 3, 7, 11]])
  1. 使用双向队列
    利用.append 和.pop 方法,我们可以把列表当作栈或者队列来用(比如,把.append
    和.pop(0) 合起来用,就能模拟栈的“先进先出”的特点)。但是删除列表的第一个元素
    (抑或是在第一个元素之前添加一个元素)之类的操作是很耗时的,因为这些操作会牵扯
    到移动列表里的所有元素。
    collections.deque 类(双向队列)是一个线程安全、可以快速从两端添加或者删除元素的数据类型。而且如果想要有一种数据类型来存放“最近用到的几个元素”,deque 也是一个很好的选择。这是因为在新建一个双向队列的时候,你可以指定这个队列的大小,如果这个队列满员了,还可以从反向端删除过期的元素,然后在尾端添加新的元素。
>>> from collections import deque
>>> dq = deque(range(10), maxlen=10)  # 指定队列的长度是10  设定后不可更改
>>> dq
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
>>> dq.rotate(3)   # 队列的旋转操作接受一个参数n,当n > 0 时,队列的最右边的n 个元素会被移动到队列的左边
>>> dq
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
>>> dq.rotate(-4)  # 。当n < 0 时,最左边的n 个元素会被移动到右边。
>>> dq
deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)
>>> dq.appendleft(-1)  # 超过最大的容量的时候  会删掉元素的  左边进  删掉右边的
>>> dq
deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
>>> dq.extend([11, 22, 33])  # 删掉元素
>>> dq
deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10)
>>> dq.extendleft([10, 20, 30, 40])
>>> dq
deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8], maxlen=10)
  1. 其他的标准库中实现的队列

    • queue

    提供了同步(线程安全)类Queue、LifoQueue 和PriorityQueue,不同的线程可以利用
    这些数据类型来交换信息。这三个类的构造方法都有一个可选参数maxsize,它接收正
    整数作为输入值,用来限定队列的大小。但是在满员的时候,这些类不会扔掉旧的元素
    来腾出位置。相反,如果队列满了,它就会被锁住,直到另外的线程移除了某个元素而
    腾出了位置。这一特性让这些类很适合用来控制活跃线程的数量。

    • multiprocessing

    这个包实现了自己的Queue,它跟queue.Queue类似,是设计给进程间通信用的。同时
    还有一个专门的multiprocessing.JoinableQueue类型,可以让任务管理变得更方便。

你可能感兴趣的:(流畅的python)