第二章 序列构成的数组

内置序列类型概览

  • 容器序列
    list、tuple 和 collections.deque 这些序列能存放不同类型的数据。
  • 扁平序列
    str、bytes、bytearray、memoryview 和 array.array,这类序列只能容纳一种类型。

说明:

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

如果按照是否被修改分类,还可以分为:

  • 可变序列
    list、bytearray、array.array、collections.deque 和 memoryview。
  • 不可变序列
    tuple、str 和 bytes。
    在这里插入图片描述

列表推导同filter和map的比较

symbols = '$¢£¥€¤'

# 把他转换成一个数值列表,并且取出大于127的值

_list = [ord(i) for i in symbols if ord(i) > 127]
print(_list)


# 使用 filter和map配合实现
__list = list(filter(lambda x: x > 127, map(ord,symbols)))

print(__list)

_ls_map = list(map(ord, symbols))

# print(list(_ls_map))

_ls_filter = filter(lambda x: x > 127, _ls_map)

print(list(_ls_filter))
print(_ls_filter)

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

  • 元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置。正是这个位置信息给数据赋予了意义。
    如果只把元组理解为不可变的列表,那其他信息——它所含有的元素的总数和它们的位置——似乎就变得可有可无。但是如果把元组当作一些字段的集合,那么数量和位置信息就变得非常重要了。

元组拆包

lax_coordinates = (33.9425, -118.408056)
latitude, longitude = lax_coordinates # 元组拆包

# 用*来处理剩下的元素
a, b, *rest = range(5)

# 交换变量
a, b = (b, a) # 等号右边其实是一个元组

# 嵌套元组拆包
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))

具名元组

  • collections.namedtuple 是一个工厂函数,它可以用来构建一个带字段名的元组和一个有
    名字的类
'''创建一个具名元组需要两个参数,一个是类名,另一个是类的各个字段的名字。后者可
以是由数个字符串组成的可迭代对象,或者是由空格分隔开的字段名组成的字符串。'''

Card = collections.namedtuple('Card', ['rank', 'suit'])
# 具名元组的属性和方法

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

  1. 可以清楚的看出切片区间里面有多少元素
    例:ls[:2] #取俩个元素
  2. 可以快速计算出切片区间的长度
    例:(stop - start) 2 - 0 = 2
  3. 利用任意的一个下标来把序列分割成不重叠的俩部分
    ls[:3] ls[3:]

对对象进行切片

seq[start:stop:step]
对于seq[start:stop:step]进行求值的时候,python会调用seq.__getitem__(slice(start, stop, step)) 

多维切片和省略

给切片赋值(右边必须是可迭代序列)
如果把切片放在赋值语句左边,或把它作为del操作的对象,我们就可可以对序列进行嫁接,切除,或者就地操作。

l = list(range(10))

# 切片赋值,右边必须是一个可迭代序列
l[2:5] = [20, 30]

对序列使用+和*

  1. +和都不修改原有的操作对象,而是构建一个全新的序列。
    2.如果在a
    n ,序列a里的元素是对其它可变对象的引用的话,由于可变对象是引用组成的,当一个引用 *3 以后,这个对象内部其实是把同一个引用复制了三份。这三个引用指向同一个地方
    例子:ls[[]] * 3 初始化一个由列表组成的列表,但是里边的三个列表是三个引用,这三个引用指向的是同一个列表。
# 如果被乘的元素是其他可变对象的引用的话,乘得的结果其实内部都是引用同一个对象
bord = [['_'] * 3 for i in range(3)]
bord[1][2] = 'X'
print(bord)
# [['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]

# --------------
反面例子:
ls = [['_'] * 3] * 3
修改其中一个会同时改变三个列表内容,因为三个引用指向同一个对象

# 追加同一个row 三次到bord1
row = ['_'] * 3
bord1 = []
for i in range(3):
    bord1.append(row)
bord1[0][0] = 'X'
print(bord1)
# [['X', '_', '_'], ['X', '_', '_'], ['X', '_', '_']]

bord2 = []
for i in range(3):
    row1 = ['_'] * 3  # 每次都新建一个列表,
    bord2.append(row1)
bord2[0][0] = 'X'
print(bord2)
[['X', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]

序列的增量赋值

  • 增量赋值运算符 += 和 *= 的表现取决于它们的第一个操作对象。
  • += 背后的特殊方法是 iadd(用于“就地加法”)。但是如果一个类没有实现这个方法的话,Python 会退一步调用 add。考虑下面这个简单的表达式:
 a += b

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

# 验证*=在不可变序列上的作用
>>> ls = [1,2,3]
>>> id(ls)
1373312520328
>>> ls *= 2
>>> ls
[1, 2, 3, 1, 2, 3]
>>> id(ls)
1373312520328  # 列表的id没有被改变
>>> t = (1,2,3)
>>> id(t)
1373312444960
>>> t *= 2
>>> t
(1, 2, 3, 1, 2, 3)
>>> id(t)
1373311779560 # 地址改变,创建了一个新的tuple
  • 对不可变序列进行重复拼接的话,效率会很低,因为每次都要创建一个新对象,而解释器需要把原来对象中的元素先复制到新的对象里,然后再追加新的元素。
  • str 是一个例外,因为对字符串做 += 实在是太普遍了,所以 CPython 对它做了优化。为 str 初始化内存的时候,程序会为它留出额外的可扩展空间,因此进行增量操作的时候,并不会涉及复制原有字符串到新位置这类操作。

比较有意思的地方

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

'''
这么写会报错,但是确实添加成功了,我的理解是,现对于元组中的列表赋值是成功的,但是把赋值成功的列表赋值回去元组的时候却失败了
'''
  • • 不要把可变对象放在元组里面。
    • 增量赋值不是一个原子操作。我们刚才也看到了,它虽然抛出了异常,但还是完成了操作。
    • 查看 Python 的字节码对我们了解代码背后的运行机制很有帮助

list.sort方法和内置函数sorted

  • list.sort 方法会就地排序列表,不会把原列表复制一份。这也是这个方法的返回值是 None 的原因,本方法不会新建一个列表。在这种情况下返回 None 其实是Python 的一个惯例:如果一个函数或者方法对对象进行的是就地改动,那它就应该返回None,好让调用者知道传入的参数发生了变动,而且并未产生新的对象
  • list.sort 相反的是内置函数 sorted,它会新建一个列表作为返回值。这个方法可以接受任何形式的可迭代对象作为参数,甚至包括不可变序列或生成器。而不管sorted 接受的是怎样的参数,它最后都会返回一个列表。

bisect(可以理解为对一个基本有序的序列进行二分查找,还可以实现插入数据时按照顺序插入)

数组 array.array 比 list 更高效

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

内存视图 (memoryview)由于暂时使用不到,没太理解,以后做补充
双向队列和其他形式的队列

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

总结:

  • python中常见的序列分类是可变序列与不可变序列,
  • 也可以分为扁平序列和容器序列,扁平序列同类型数据,体积小,速度快。容器序列比较灵活,如果容器序列遇到可变对象可能会遇到一些坑。
  • 列表推导式,字典推导式,生成器表达式
  • 元组在python里扮演了俩个角色,1 不可变的列表,2 无名称的字段的记录,具名元组实例更节省空间, ._asdict() 方法可以把记录变成OrderedDict类型。
  • 序列切片,可以支持多维切片和省略
  • 增量赋值 +=和*=会区别对待可变和不可变序列,遇到不可变序列的时候,这种操作会生成新的序列,如果是可变序列,这个操作会就地修改。
  • sort方法和sorted函数,都可以接受一个参数来指定排序算法如何排序,key参数同样可以用于min和max函数,
  • 插入新元素时保持有序序列的顺序,bisect.insort, bisect.bisect的作用是快速查找。
  • python支持array.array, collections.deque 等扩展类型
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值