流畅的Python学习笔记——数据模型与数据结构(一)

Python数据模型

Python 有很多内置方法,这些内置方法其实也是类对象,只不过在类的内部实现了一系列的魔法方法

魔法方法:以双下划线开头和结尾的方法,如__getitem__方法,这种方法也叫做双下方法(dunder method)

在自定义类中使用重写这些特殊方法,可以使类获得以下功能:

  • 迭代
  • 集合类
  • 属性访问
  • 运算符重载
  • 函数和方法的调用
  • 对象的创建和销毁
  • 字符串表示形式和格式化
  • 管理上下文(即with块)

使用特殊方法

在类中重写了特殊方法后,在实例化类的对象后调用这些特殊方法是隐式的,比如说:my_object = MyObject(), 在MyObject类中重写了__len__方法,就可以使用Python内置方法len(my_object)来获取长度,而不是使用my_object.__len__()这种写法。

很多时候,特殊方法的调用是隐式的,比如 for i in x: 这个语句,背后其实用的是
iter(x),而这个函数的背后则是 x.iter() 方法。当然前提是这个方法在 x 中被实现了。

通过内置的函数(例如 len、 iter、 str,等等)来使用特殊方法是最好的选择。这些内置函数不仅会调用特殊方法,通常还提供额外的好处,而且对于内置的类来说,它们的速度
更快。

模拟数值类型

自定义二维向量向量类实现,要求如下:

  • 两个二维向量可以相加

    >>> v1 = Vector(2, 4)
    >>> v2 = Vector(2, 1)
    >>> v1 + v2
    Vector(4, 5)
    
  • 可以对二维向量求模

    >>> v = Vector(3, 4)
    >>> abs(v)
    5.0
    
  • 向量与数相乘:

    >>> v * 3
    Vector(9, 12)
    >>> abs(v * 3)
    15.0
    

类中要实现 __repr__、 __abs__、 __add__ 和 __mul__:

from math import hypot
class Vector:
	def __init__(self, x=0, y=0):
		self.x = x
		self.y = y
	def __repr__(self):
		return 'Vector(%r, %r)' % (self.x, self.y)
	def __abs__(self):
		return hypot(self.x, self.y)
	def __bool__(self):
		return bool(abs(self))
	def __add__(self, other):
		x = self.x + other.x
		y = self.y + other.y
		return Vector(x, y)
	def __mul__(self, scalar):
		return Vector(self.x * scalar, self.y * scalar)

字符串表示形式

Python处理字符串的有两个内置函数:

  • str()

    该内置函数内部实现了__str__方法,当我们使用print打印的时候调用的就是这个特殊方法,该方法打印出来的字符串对终端用户更友好。

  • repr()

    该内置函数内部实现了__repr__方法,当我们使用%r来获取变量时使用的就是这个特殊方法,该方法返回的字符串更准确,无歧义。

  • 什么时候使用哪个内置函数没有固定的,根据实际情况而定:

    一般调试和记录日志的时候使用repr(),给终端用户看的时候用str()

算术运算符

  • 通过重写__add__和__mul__方法来重写+ 和 * 这两个运算符。
  • 这两个方法的返回值都是新创建的对象,而不会原有对象。

自定义的布尔值

  • bool()实现了__bool__方法, 如果不存在 __bool__方法,那么 bool(x)会尝试调用 x.__len__()。若返回 0,则bool会返回False;否
    则返回 True

  • 在前边自定义的向量类中实现了__bool__方法来判断是否是一个向量更加高效的写法:

    def __bool__(self):
    	return bool(self.x or self.y)
    

序列构成的数组

Python中的数据结构都是序列构成的数组,它们都共用一套丰富的操作:迭代、切片、排序、拼接。

根据存放的数据的类型分类:

  • 容器序列

    list、tuple 和 collections.deque 这些序列能存放不同类型的数据。该序列存放的是对象的引用

  • 扁平序列

    str、bytes、 bytearray、 memoryview 和 array.array,这类序列只能容纳一种类型。 该序列存放的是对象的值

按照能否被修改分类:

  • 可变序列类型

    list、bytearray、 array.array、 collections.deque 和 memoryview。

  • 不可变序列类型

    tuple、 str 和 bytes。

列表推导式和生成器

  • 列表推导式从某种意义上讲可以将代码的可读性和效率变高,但是当列表推导中的代码超过两行的时候,就会使可读性变差,就要考虑是否使用for循环。
  • 一般只用列表推导式创建新的列表,并且尽量保持简短。
  • Python 会忽略代码里 []、 {} 和 () 中的换行,因此如果代码里有多行的列
    表、列表推导、生成器表达式、字典这一类的,可以省略不太好看的续行符 \。

列表推导式同filter和map的比较

>>> symbols = '$¢£¥€¤'
>>> beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
>>> beyond_ascii
[162, 163, 165, 8364, 164]
>>> beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))
>>> beyond_ascii
[162, 163, 165, 8364, 164]

生成器表达式

  • 把列表推导式的方括号换成圆括号。

  • 用生成器表达式初始化元组和数组:

    >>> symbols = '$¢£¥€¤'
    >>> tuple(ord(symbol) for symbol in symbols) ➊
    (36, 162, 163, 165, 8364, 164)
    >>> import array
    >>> array.array('I', (ord(symbol) for symbol in symbols)) ➋
    array('I', [36, 162, 163, 165, 8364, 164])
    

    ➊ 如果生成器表达式是一个函数调用过程中的唯一参数,那么不需要额外再用括号把它围起来。
    ➋ array 的构造方法需要两个参数,因此括号是必需的。

元组

  • 元组不仅仅是一个不可变对象,它还可以用于没有字段名的记录。

元组和记录

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

  • 把元组当做记录:

    >>> lax_coordinates = (33.9425, -118.408056) ➊
    >>> city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014) ➋
    >>> traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ➌
    ... ('ESP', 'XDA205856')]
    >>> for passport in sorted(traveler_ids): ➍
    ... print('%s/%s' % passport) ➎
    ...
    BRA/CE342567
    ESP/XDA205856
    USA/31195855
    >>> for country, _ in traveler_ids: ➏
    ... print(country)
    ...
    USA
    BRA
    ESP
    

    ➊ 洛杉矶国际机场的经纬度。
    ➋ 东京市的一些信息:市名、年份、人口(单位:百万)、人口变化(单位:百分比)和
    面积(单位:平方千米)。
    ➌ 一个元组列表,元组的形式为 (country_code, passport_number)。
    ➍ 在迭代的过程中, passport 变量被绑定到每个元组上。
    ➎ % 格式运算符能被匹配到对应的元组元素上。
    ➏ for 循环可以分别提取元组里的元素,也叫作拆包(unpacking)。因为元组中第二个元素对我们没有什么用,所以它赋值给“_”占位符。

元组拆包

  • 把元组中的多个值使用一行代码同时赋值给多个变量叫做元组拆包。

  • 元组拆包需要注意的地方就是变量必须和元组中的元素的数量一致,如果不想一一对应接收就必须使用可变参数*args来接收。

  • 嵌套元组也可以通过拆包:

    metro_areas = [
    	('Tokyo','JP',36.933,(35.689722,139.691667)), # ➊
    	('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
    	('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
    	('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
    	('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
    ]
    print('{:15} | {:^9} | {:^9}'.format('', 'lat.', 'long.'))
    fmt = '{:15} | {:9.4f} | {:9.4f}'
    for name, cc, pop, (latitude, longitude) in metro_areas: # ➋
    	if longitude <= 0: # ➌
    		print(fmt.format(name, latitude, longitude))
    

    ➊ 每个元组内有 4 个元素,其中最后一个元素是一对坐标。
    ➋ 我们把输入元组的最后一个元素拆包到由变量构成的元组里,这样就获取了坐标。
    ➌ if longitude <= 0: 这个条件判断把输出限制在西半球的城市。

    结果:

    				| lat. 	  | long.
    Mexico City 	| 19.4333 | -99.1333
    New York-Newark | 40.8086 | -74.0204
    Sao Paul 		| -23.5478| -46.6358
    

具名元组

  • 使用工厂函数:collections.namedtuple可以构建一个带字段和一个有名字的类。

    >>> from collections import namedtuple
    >>> City = namedtuple('City', 'name country population coordinates') ➊
    >>> 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))])
    >>> for key, value in delhi._asdict().items():
    		print(key + ':', value)
    		
    name: Delhi NCR
    country: IN
    population: 21.935
    coordinates: LatLong(lat=28.613889, long=77.208889)
    >>>
    

切片

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

在Python中以0作为起始下标,切片和区间忽略最后一个元素的好处如下:

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

对对象进行切片

使用形如s[a:b:c]的形式进行切片返回的是一个切片对象:slice(a, b, c),我们可以给切片对象命名,然后使用命名后的切片形式:

  • 纯文本文件形式的收据以一行字符串的形式被解析
>>> invoice = """
... 0.....6................................40........52...55........30
... 1909 Pimoroni PiBrella $17.50 3 $52.50
... 1489 6mm Tactile Switch x20 $4.95 2 $9.90
... 1510 Panavise Jr. - PV-201 $28.00 1 $28.00
... 1601 PiTFT Mini Kit 320x240 $34.95 1 $34.95
... """
>>> SKU = slice(0, 6)
>>> DESCRIPTION = slice(6, 40)
>>> UNIT_PRICE = slice(40, 52)
>>> QUANTITY = slice(52, 55)
>>> ITEM_TOTAL = slice(55, None)
>>> line_items = invoice.split('\n')[2:]
>>> for item in line_items:
... 	print(item[UNIT_PRICE], item[DESCRIPTION])
...
    $17.50 Pimoroni PiBrella
    $4.95 6mm Tactile Switch x20
    $28.00 Panavise Jr. - PV-201
    $34.95 PiTFT Mini Kit 320x240

多维切片和省略

  • [] 运算符里还可以使用以逗号分开的多个索引或者是切片,外部库 NumPy 里就用到了这个特性,二维的 numpy.ndarray就可以用 a[i, j]这种形式来获取,或是用 a[m:n, k:l]的方式来得到二维切片
  • **省略(ellipsis)**的正确书写方法是三个英语句号(…),在 NumPy 中, ...用作多维数组切片的快捷方式。如果 x 是四维数组,那么 x[i, ...]就是 x[i, :, :, :]的缩写。

给切片赋值

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

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

>>> l[2:5] = 100
Traceback (most recent call last):
	File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable

对序列使用+和*

  • + 和 * 不修改原有的操作对象,而是构建一个全新的序列

  • 当我们使用*来初始化一个有列表组成的列表的时候,得到的结果可能不是我们想要的:

    如:初始化一个包含三个相同列表的列表:

    • 正确的初始化写法:

      >>> board = [['_'] * 3 for i in range(3)] 
      >>> board
      [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
      >>> board[1][2] = 'X' 
      >>> board
      [['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
      
    • 错误的初始化写法:

      >>> weird_board = [['_'] * 3] * 3 
      >>> weird_board
      [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
      >>> weird_board[1][2] = 'O' 
      >>> weird_board
      [['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]
      

      当改变其中一个列表的时候,另外两个列表也变化了,错误的结果。

      其实, 用my_list = [[]] * 3 来初始化一个由列表组成的列表,列表里包含的 3 个元素其实是 3 个引用,而且这 3 个引用指向的都是同一个列表,所以改变其中一个的时候,另外两个也会变化。

序列的增量赋值

  • 增量赋值用算符+=*=的表现取决于它们的第一个操作对象,+= 背后的特殊方法是__iadd__(用于就地加法),但是如果一个类没有实现这个方法,Python回去调用__add__,就会将相加的值赋给一个中间变量,然后再将中间变量的值赋给第一个操作对象。

  • 同样的*=背后调用的是__imul__*=在可变和不可变序列上的作用:

    >>> l = [1, 2, 3]
    >>> id(l)
    4311953800 ➊
    >>> l *= 2
    >>> l
    [1, 2, 3, 1, 2, 3]
    >>> id(l)
    4311953800 ➋
    >>> t = (1, 2, 3)
    >>> id(t)
    4312681568 ➌
    >>> t *= 2
    >>> id(t)
    4301348296 ➍
    

    ➊ 刚开始时列表的 ID。
    ➋ 运用增量乘法后,列表的 ID 没变,新元素追加到列表上。
    ➌ 元组最开始的 ID。
    ➍ 运用增量乘法后,新的元组被创建。

    • 对不可变序列进行重复拼接操作的话,效率会很低,因为每次都有一个新对象,而解释器
      需要把原来对象中的元素先复制到新的对象里,然后再追加新的元素。
  • 不要把可变对象放到元组里,更改元组里的可变对象会成功,但是也会报异常:

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

    解释器的步骤如下:

    1. 现将元组里的列表改变,这不不会出异常
    2. 再将改变后的值赋值给元组指定的位置,因为元组是不可变对象,所以这一步会报错。
    3. 但是查看t结果是变了的,因为t[3]是一个引用,实际的列表已经发生了变化。

list.sort方法和内置函数sorted

  • sort方法是就地排序,也就是在原对象上排序,不返回新的对象,所以返回的是None.。
  • sorted方法会返回排完序后的新的对象,原对象不改变。

其实不光光是sort排序方法返回None,有很多就地操作的内置函数返回的都是None,如random.shuffle

  • 这样做的目的其实是为了让使用者知道对原有对象进行了变更,所以返回None.

排序方法接受两个参数:

  • reverse:降序排序

  • key:指定排序的规则,如:

    key=len根据元素的长度排序,key=str.lower忽略大小写的排序

用bisect来管理已排序的序列

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

用bisect来搜索

当我们需要往一个排序过的序列中插入一个元素使该序列任然保持有序,就需要先知道插入元素的位置,可以使用bisect模块来获取该元素插入的位置.

用法bisect(haystack, needle)

  • haystack必须是一个有序的序列

  • needle需要搜索位置的元素

  • 当插入元素在有序序列中存在相等的元素时,bisect模块就提供了两种方法:

    • bisect.bisect_left(haystack, needle)

      该方法返回的位置是将元素插入有序序列中相等元素的左边的位置(也就是序列中相等元素的位置)

    • bisect.bisect_right(haystack, needle)或者bisect.bisect(haystack, needle)

      bisect模块中默认的是此方法,该方法返回的位置是将元素插入有序序列中相等元素的右边的位置。

  • 该模块是基于二分查找的算法,所以有两个可选参数来限定搜寻的范围:

    • lo:默认值是0
    • hi:默认值是序列的长度,即len()作用于该序列的返回值。
  • import bisect
    import sys
    HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30]
    NEEDLES = [0, 1, 2, 5, 8, 10, 22, 23, 29, 30, 31]
    
    ROW_FMT = '{0:2d} @ {1:2d} {2}{0:<2d}'
    
    def demo(bisect_fn):
    	for needle in reversed(NEEDLES):
    		position = bisect_fn(HAYSTACK, needle) ➊
    		offset = position * ' |' ➋
    		print(ROW_FMT.format(needle, position, offset)) ➌
    		
    if __name__ == '__main__':
    
    	if sys.argv[-1] == 'left': ➍
    		bisect_fn = bisect.bisect_left
    	else:
    		bisect_fn = bisect.bisect
    		print('DEMO:', bisect_fn.__name__) ➎
    		print('haystack ->', ' '.join('%2d' % n for n in HAYSTACK))
    		demo(bisect_fn)
    

    ➊ 用特定的 bisect 函数来计算元素应该出现的位置。
    ➋ 利用该位置来算出需要几个分隔符号。
    ➌ 把元素和其应该出现的位置打印出来。
    ➍ 根据命令上最后一个参数来选用 bisect 函数。
    ➎ 把选定的函数在抬头打印出来。

    输出结果如下:
    在这里插入图片描述

  • 实际的例子:根据一个分数,找到它所对应的成绩:

    >>> def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
    ... i = bisect.bisect(breakpoints, score)
    ... return grades[i]
    ...
    >>> [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
    ['F', 'A', 'C', 'C', 'B', 'A', 'A']
    

用bisect.insort插入新元素

bisect.insort的作用就是向一个有序序列中插入一个元素任然保持有序:

用法insort(seq, item)

  • seq:有序序列

  • item:插入的元素

  • import bisect
    import random
    
    	SIZE=7
    	
    	random.seed(1729)
    	
    	my_list = []
        for i in range(SIZE):
            new_item = random.randrange(SIZE*2)
            bisect.insort(my_list, new_item)
            print('%2d ->' % new_item, my_list)
    

    结果如下:
    在这里插入图片描述

  • insortbisect 一样,有 lohi 两个可选参数用来控制查找的范围。它也有个变体叫
    insort_left,这个变体在背后用的是 bisect_left

当列表不是首选时

  • 列表使用起来简单灵活,但是在合适的场景使用合适的数据结构将会有质的提升。

数组

  • 如果我们需要一个只包含数字的列表,那么数组比列表高效。

  • 创建数组的时候需要指定类型码

  • 数组除了支持所有跟可变序列有关的操作,包括.pop、.insert和.extend,还提供从文件读取存入文件的更快的方法

    如:创建一个有1000万个随机浮点数的数组,把这个数组存放到文件中,再从文件读取这个数组:

    >>> from array import array ➊
    >>> from random import random
    >>> floats = array('d', (random() for i in range(10**7))) ➋
    >>> floats[-1] ➌
    0.07802343889111107
    >>> fp = open('floats.bin', 'wb')
    >>> floats.tofile(fp) ➍
    >>> fp.close()
    >>> floats2 = array('d') ➎
    >>> fp = open('floats.bin', 'rb')
    >>> floats2.fromfile(fp, 10**7) ➏
    >>> fp.close()
    >>> floats2[-1] ➐
    0.07802343889111107
    >>> floats2 == floats ➑
    True
    

    ➊ 引入 array 类型。
    ➋ 利用一个可迭代对象来建立一个双精度浮点数组(类型码是 ‘d’),这里我们用的可迭
    代对象是一个生成器表达式。
    ➌ 查看数组的最后一个元素。
    ➍ 把数组存入一个二进制文件里。
    ➎ 新建一个双精度浮点空数组。
    ➏ 把 1000 万个浮点数从二进制文件里读取出来。
    ➐ 查看新数组的最后一个元素。
    ➑ 检查两个数组的内容是不是完全一样。

  • 列表和数组的属性和方法(不包含过期的数组方法以及那些由对象实现的方法)

列表数组
s.__add(s2)__s + s2 ,拼接
s.__iadd(s2)__s += s2 ,就地拼接
s.append(e)在尾部添加一个元素
s.byteswap翻转数组内每个元素的字节序列,转换字节序
s.clear()删除所有元素
s.__contains__(e)s 是否含有 e
s.copy()对列表浅复制
s.__copy__()对 copy.copy 的支持
s.count(e)s 中 e 出现的次数
s.__deepcopy__()对 copy.deepcopy 的支持
s.__delitem__(p)删除位置 p 的元素
s.extend(it)将可迭代对象 it 里的元素添加到尾部
s.frombytes(b)将压缩成机器值的字节序列读出来添加到尾部
s.fromfile(f, n)将二进制文件 f 内含有机器值读出来添加到尾部,最多添加 n 项
s.fromlist(l)将二进制文件 f 内含有机器值读出来添加到尾部,最多添加 n 项
s.fromlist(l)将列表里的元素添加到尾部,如果其中任何一个元素导致了TypeError 异常,那么所有的添加都会取消
s.__getitem__(p)s[p],读取位置 p 的元素
s.index(e)找到 e 在序列中第一次出现的位置
s.insert(p, e)在位于 p 的元素之前插入元素 e
s.itemsize数组中每个元素的长度是几个字节
s.__iter__()返回迭代器
s.__len__()len(s),序列的长度
s.__mul__(n)s * n,重复拼接
s.__imul__(n)s *= n ,就地重复拼接
s.__rmul__(n)n * s ,反向重复拼接
s.pop([p])删除位于 p 的值并返回这个值, p 的默认值是最后一个元素的位置
s.remove(e)删除序列里第一次出现的 e 元素
s.reverse()就地调转序列中元素的位置
s.__reversed__()返回一个从尾部开始扫描元素的迭代器
s.__setitem__(p, e)s[p] = e,把位于 p 位置的元素替换成 e
s.sort([key], [revers])就地排序序列,可选参数有 key 和 reverse
s.tobytes()把所有元素的机器值用 bytes 对象的形式返回
s.tofile(f)把所有元素以机器值的形式写入一个文件
s.tolist()把数组转换成列表,列表里的元素类型是数字对象
s.typecode返回只有一个字符的字符串,代表数组元素在 C 语言中的类型

内存视图

  • memoryview是一个内置类,它能让用户在不复制内容的情况下操作同一个数组的不同切
    片。
  • 内存视图其实是泛化和去数学化的 NumPy 数组。它让你在不需要复制内容的前提下,
    在数据结构之间共享内存。

双向队列和其它形式的队列

  • collections.deque 类(双向队列)是一个线程安全、可以快速从两端添加或者删除元素的
    数据类型。

  • 如果想要有一种数据结构存放“最近用到的几个元素”, deque(双向队列)是一个好的选择

    >>> from collections import deque
    >>> dq = deque(range(10), maxlen=10) ➊
    >>> dq
    deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
    >>> dq.rotate(3) ➋
    >>> dq
    deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
    >>> dq.rotate(-4)
    >>> 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)
    

    ➊ maxlen 是一个可选参数,代表这个队列可以容纳的元素的数量,而且一旦设定,这个
    属性就不能修改了。
    ➋ 队列的旋转操作接受一个参数 n,当 n > 0 时,队列的最右边的 n 个元素会被移动到队
    列的左边。当 n < 0 时,最左边的 n 个元素会被移动到右边。
    ➌ 当试图对一个已满(len(d) == d.maxlen)的队列做尾部添加操作的时候,它头部的元
    素会被删除掉。注意在下一行里,元素 0 被删除了。
    ➍ 在尾部添加 3 个元素的操作会挤掉 -1、 1 和 2。
    ➎ extendleft(iter) 方法会把迭代器里的元素逐个添加到双向队列的左边,因此迭代器里
    的元素会逆序出现在队列里。

  • 双向队列实现了大部分列表所拥有的方法,但是双向队列只对头尾的操作进行了优化,从队列中间删除元素的操作会慢一些。

  • appendpopleft都是原子操作,也就说是 deque可以在多线程程序中安全地当作先进先
    出的栈使用,而使用者不需要担心资源锁的问题。

  • 列表和双向队列的方法(不包括由对象实现的方法)

列表双向队列
s.append(e)添加一个元素到最右侧(到最后一个元素之后)
s.appendleft(e)添加一个元素到最左侧(到第一个元素之前)
s.clear()删除所有元素
s.count(e)s 中 e 出现的次数
s.extend(i)将可迭代对象 i 中的元素添加到尾部
s.extendleft(i)将可迭代对象 i 中的元素添加到头部
s.index(e)找到 e 在序列中第一次出现的位置
s.insert(p, e)在位于 p 的元素之前插入元素 e
s.pop()移除最后一个元素并返回它的值
s.popleft()移除第一个元素并返回它的值
s.remove(e)移除序列里第一次出现的 e 元素
s.reverse()调转序列中元素的位置
s.rotate(n)把 n 个元素从队列的一端移到另一端
s.sort([key], [revers])就地排序序列,可选参数有 key 和 reverse

注: a_list.pop(p) 这个操作只能用于列表,双向队列的这个方法不接收参数。

  • 除了 deque 之外,还有些其他的 Python 标准库也有对队列的实现 :

  • queue

提供了**同步(线程安全)**类 `Queue`、` LifoQueue `和` PriorityQueue`,不同的线程可以利用这些数据类型来交换信息。这三个类的构造方法都有一个可选参数 maxsize,它接收正整数作为输入值,用来限定队列的大小。但是在满员的时候,这些类不会扔掉旧的元素来腾出位置。相反,如果**队列满了,它就会被锁住,直到另外的线程移除了某个元素而腾出了位置**。这一特性让这些类很适合用来控制活跃线程的数量。  
  • multiprocessing
这个包实现了自己的 `Queue`,它跟 `queue.Queue` 类似,是设计给**进程间通信**用的。同时还有一个专门的 `multiprocessing.JoinableQueue `类型,可以让任务管理变得更方便。  
  • asyncio

    为异步编程里的任务管理提供了专门的便利。
    调转序列中元素的位置 |
    | s.rotate(n) | | • | 把 n 个元素从队列的一端移到另一端 |
    | s.sort([key], [revers]) | • | | 就地排序序列,可选参数有 key 和 reverse |

注: a_list.pop(p) 这个操作只能用于列表,双向队列的这个方法不接收参数。

  • 除了 deque 之外,还有些其他的 Python 标准库也有对队列的实现 :

  • queue

提供了**同步(线程安全)**类 `Queue`、` LifoQueue `和` PriorityQueue`,不同的线程可以利用这些数据类型来交换信息。这三个类的构造方法都有一个可选参数 maxsize,它接收正整数作为输入值,用来限定队列的大小。但是在满员的时候,这些类不会扔掉旧的元素来腾出位置。相反,如果**队列满了,它就会被锁住,直到另外的线程移除了某个元素而腾出了位置**。这一特性让这些类很适合用来控制活跃线程的数量。  
  • multiprocessing
这个包实现了自己的 `Queue`,它跟 `queue.Queue` 类似,是设计给**进程间通信**用的。同时还有一个专门的 `multiprocessing.JoinableQueue `类型,可以让任务管理变得更方便。  
  • asyncio

    为异步编程里的任务管理提供了专门的便利。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一切如来心秘密

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值