fluent python豆瓣_fluent python学习笔记——python序列

python序列类型

容器序列

容器序列能够存放不同类型的数据,像list、tuple和collections.deque。

扁平序列

扁平序列只能存在一种类型的数据,像str、bytes、bytearray、memoryview、array.array。

注意:容器序列存放是任意类型的对象的引用,而扁平序列存在是值而不是引用,是一段连续的内存空间。

按照是否能被修改来区分:

可变序列

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

memoryview

不可变序列

tuple、str 和 bytes

list——列表推倒

利用列表推导创建列表具有很好的可读性

比较下面两段代码

symbols = '$¢£¥€¤'

codes = []

for symbol in symbols:

codes.append(ord(symbol))

codes

Out[5]:

[36, 162, 163, 165, 8364, 164]

symbols = '$¢£¥€¤'

codes = [ord(symbol) for symbol in symbols]

codes

Out[12]:

[36, 162, 163, 165, 8364, 164]

利用列表推导的代码显得相当简洁。列表推导还能生成多重for循环的列表,如下代码所示:

a = ['A','B','C','D']

b = [1,2,3,4]

c = [(i, j) for i in a for j in b]

c

Out[5]:

[('A', 1),

('A', 2),

('A', 3),

('A', 4),

('B', 1),

('B', 2),

('B', 3),

('B', 4),

('C', 1),

('C', 2),

('C', 3),

('C', 4),

('D', 1),

('D', 2),

('D', 3),

('D', 4)]

d = [(i, j) for j in b for i in a]

d

Out[9]:

[('A', 1),

('B', 1),

('C', 1),

('D', 1),

('A', 2),

('B', 2),

('C', 2),

('D', 2),

('A', 3),

('B', 3),

('C', 3),

('D', 3),

('A', 4),

('B', 4),

('C', 4),

('D', 4)]

用列表推导还能替代filter和map,避免使用难以理解的lambda表达式,

symbols = '$¢£¥€¤'

beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))

beyond_ascii

Out[12]:

[162, 163, 165, 8364, 164]

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

beyond_ascii

Out[14]:

[162, 163, 165, 8364, 164]

元组

元组的意义不仅仅在于是不可变序列,更在于它可以与记录关联起来,赋予它特殊的意义。

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

这里每个元组里的每一项都有了特定的含义。下面看看元组的拆包。

简单拆包

需要说明的是拆包可以运用到任何可迭代对象上。

先看最简单的拆包,平行赋值

lax_coordinates = (33.9425, -118.408056)

latitude, longitude = lax_coordinates

latitude

Out[10]:

33.9425

longitude

Out[11]:

-118.408056

利用平行赋值简便的交换两个变量的值

a=2

b=3 a,b=b,a a

Out[15]:

3

b

Out[16]:

2

可以用*处理剩下的元素

a,b,*rest = range(6)

a

Out[19]:

0

rest

Out[20]:

[2, 3, 4, 5]

b

Out[21]:

1

a,*rest,b = range(6)

a

Out[23]:

0

b

Out[24]:

5

rest

Out[25]:

[1, 2, 3, 4]

嵌套拆包

拆包还可以嵌套进行,这个时候需要注意表达式的嵌套结构要和元组的嵌套结构一致

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))

上述代码输出如下

| lat. | long.

Mexico City | 19.4333 | -99.1333

New York-Newark | 40.8086 | -74.0204

Sao Paulo | -23.5478 | -46.6358

具名元组

作为记录使用,我们很多时候希望要有一个字段名,虽然元组本身无法满足要求,但是可以利用库中的collections.namedtuple来实现。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' >>>

具名元组还有一些特有的属性和方法:

>>> 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)

_fields属性是一个包含这个类所有字段名称的元组,_make()方法能够接受一个可迭代对象来生成这个类的一个实例,_asdict() 把具名元组以collections.OrderedDict 的形式返回。

切片

切片的功能学过python的都了解,这里主要涉及一些切片需要注意的地方。

切片会忽略最后一个元素这符合python和C都以0作为起始是相符的

>>> s = [1,2,3,4,5,6]

>>> s[1:3]

[2, 3]

对对象进行切片

还可以用s[a:b:c]的形式对s在a和b之间以c为间隔取值,c的值可以为负,这时为反向取值

>>> s = [1,2,3,4,5,6,7,8,9,10]

>>> s[1:9:2]

[2, 4, 6, 8]

>>> s[9:1:-2]

[10, 8, 6, 4]

>>> s[::-1]

[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

a:b:c这种用法的本质是在调用slice(start,stop,step),对seq[start:stop:step] 进行求值的时候,Python 会调用 seq.__getitem__(slice(start, stop, step))。

给切片赋值

切片还有一个强大的功能就是可以直接给切片赋值以修改原序列。

>>> s = list(range(10))

>>> s

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> s[3:6] = [10,20]

>>> s

[0, 1, 2, 10, 20, 6, 7, 8, 9]

>>> s[1:3] = [30,40,50,60]

>>> s

[0, 30, 40, 50, 60, 10, 20, 6, 7, 8, 9]

>>> s[1:5] = 20

Traceback (most recent call last):

File "", line 1, in

s[1:5] = 20

TypeError: can only assign an iterable

>>> s[1:5] = [20]

>>> s

[0, 20, 10, 20, 6, 7, 8, 9]

从上面代码可以看出,给切片复值的值必须也是序列,哪怕只有一个值,也要用序列的形式。

对序列使用+和*

+和*都是对序列的拼接操作,+和*操作不会修改原序列,而是生成一个新的序列。

>>> s1 = [1,2,3]

>>> s2 = [4,5,6]

>>> s1 + s2

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

>>> s1

[1, 2, 3]

>>> s1 * 4

[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]

>>> s1

[1, 2, 3]

>>> 3*s2

[4, 5, 6, 4, 5, 6, 4, 5, 6]

>>> s2

[4, 5, 6]

>>>

关于需要注意的一点:如果序列内的元素是其他可变对象的引用的话,就需要格外小心了。看下面这个由列表构成列表的例子:

创建一个由列表组成的列表,我们可能会写出下面这样的代码

weird_board = [['_'] * 3] * 3

weird_board[1][2] = 'O'

这样的结果会是什么,我们看下每步的结果:

>>> weird_board = [['_'] * 3] * 3 >>> weird_board

[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]

weird_board[1][2] = 'O' >>> weird_board

[['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]

完全不是我们想要的结果,为什么会出现这种现象?这里第一步中的[['_'] * 3]会形成序列[['_', '_', '_']],而[['_', '_', '_']]*3形成的三个序列都是这个序列的引用,所以就会出现上面的情况。

正确的做法应该像下面这样:

>>> board = [['_'] * 3 for i in range(3)]

>>> board

[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]

>>> board[1][2] = 'X' >>> board

[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]

序列的增量赋值

增量赋值原理上都是一致的,这里主要集中在+=上。

+=操作背后实际是调用了__iadd__方法,如果一个类没有实现这个方法的话,Python 会退一步调用 __add__ 方法。同时,可变序列就地改动,不可变序列会生成一个新的对象。

>>> s=[1,2,3]

>>> id(s)

48702528 >>> s+=[4,5,6]

>>> s

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

>>> id(s)

48702528 >>> s*=3 >>> s

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

>>> id(s)

48702528 >>> t=(1,2,3)

>>> id(t)

48765168 >>> t+=(4,5,6)

>>> t

(1, 2, 3, 4, 5, 6)

>>> id(t)

48202224

一个关于+=的陷阱

下面这段代码执行后会发生什么情况

>>> t = (1,2,[3,4])

>>> t[2] += [5,6]

相信很多人都知道会抛出异常,因为元组是不可变序列。我们来看看执行结果,的确如此

>>> t = (1,2,[3,4])

>>> t[2] += [5,6]

Traceback (most recent call last):

File "", line 1, in

t[2] += [5,6]

TypeError: 'tuple' object does not support item assignment

再看看现在t的值呢?

>>> t

(1, 2, [3, 4, 5, 6])

t的值也改变了,这是怎么回事呢?我们利用Python Tutor(http://www.pythontutor.com)这个可视化分析工具来看看运行情况。(这里强烈推荐这个工具,能够动态展示每句代码执行后各个变量的变化情况)。

再来看看相应的背后的字节码

>>> import dis

>>> dis.dis('s[a]+=b')

1 0 LOAD_NAME 0 (s)

2 LOAD_NAME 1 (a)

4 DUP_TOP_TWO

6 BINARY_SUBSCR

8 LOAD_NAME 2 (b)

10 INPLACE_ADD

12 ROT_THREE

14 STORE_SUBSCR

16 LOAD_CONST 0 (None)

18 RETURN_VALUE

首先将 s[a] 的值存入 TOS(栈的顶端),然后计算 TOS += b。这一步能够完成,是因为 TOS 指向的是一个可变对象,最后s[a] = TOS 赋值。这一步失败,是因为 s 是不可变的元组。

排序和搜索

排序

list.sort会对列表进行就地排序,而python的内置函数sorted则是重新创建一个对象返回,它接受任何可迭代对象作为参数。

>>> s=[3,5,2,1,6,7,9,8]

>>> s_new = sorted(s)

>>> s

[3, 5, 2, 1, 6, 7, 9, 8]

>>> s_new

[1, 2, 3, 5, 6, 7, 8, 9]

>>> s.sort()

>>> s

[1, 2, 3, 5, 6, 7, 8, 9]

搜索

排过序的序列可以进行二分查找,python标准库的内置模块 bisect 提供了二分查找算法。bisect(haystack, needle) 在 haystack里搜索needle的位置,该位置满足的条件是,把 needle 插入这个位置之后,haystack 还能保持升序。

>>> bisect.bisect(s,3)

3 >>> bisect.bisect(s,8)

7 >>> bisect.bisect(s,8.5)

7

用bisect.insort还可以在一个有序的序列里插入新元素从而保持序列仍然有序。

>>> bisect.insort(s,8.5)

>>> s

[1, 2, 3, 5, 6, 7, 8, 8.5, 9]

列表之外的选择

列表虽然灵活而且简单,但是有些时候面对各类需求的时候,我们可能还有更好的选择。当数据量较大时,比如,要存放 1000 万个浮点数的话,数组(array)的效率要高得多,因为数组在背后存的并不是 float 对象,而是数字的机器翻译,也就是字节表述。这一点就跟 C 语言中的数组一样。

数组

如果我们需要一个只包含数字的列表,那么 array.array 比 list 更高效。同时,数组不仅包括 .pop、.insert 和.extend,还提供从文件读取和存入文件的更快的方法,如.frombytes 和 .tofile。

创建一个array时,需要指定其类型,这个类型的符号与相应的类型的对应关系在标准库中能够查到。

下面的例子展示了从创建一个有 1000 万个随机浮点数的数组开始,到如何把这个数组存放到文件里,再到如何从文件读取这个数组。

>>> from array import array

>>> from random import random

>>> floats = array('d', (random() for i in range(10**7)))

>>> floats[-1]

0.4628972061976403 >>> 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.4628972061976403 >>> floats2 == floats

True

内存视图

memoryview 是一个内置类,它能让用户在不复制内容的情况下操作同一个数组的不同切片。memoryview.cast 的概念跟数组模块类似,能用不同的方式读写同一块内存数据,而且内容字节不会随意移动。这与C语言中的类型转换差不多。

>>> numbers = array('h', [-2, -1, 0, 1, 2])

>>> memv = memoryview(numbers)

>>> len(memv)

5 >>> memv[0]

-2 >>> memv_oct = memv.cast('B')

>>> memv_oct

>>> memv_oct.tolist()

[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]

>>> memv_oct[5] = 4 >>> numbers

array('h', [-2, -1, 1024, 1, 2])

>>>

这里利用含有 5 个短整型有符号整数的数组(类型码是 ‘h’)创建一个memoryview,然后创建一个 memv_oct,这一次是把 memv 里的内容转换成 ‘B’ 类型,也就是无符号字符。把位于位置 5 的字节赋值成 4,因为我们把占 2 个字节的整数的高位字节改成了 4,所以这个有符号整数的值就变成了 1024。

双端队列

collections.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 是一个可选参数,设定之后就不能修改,当队列中的元素超出这个值之后,就会自动删除多出的元素,如果从左端加入,就删除最右端的,如果从最右端加入,则从最左端删除。extendleft()方法是迭代插入的,所以插入后的顺序与原来的是相反的。

此外,append 和 popleft 都是原子操作,也就说是 deque 可以在多线程程序

中安全地当作先进先出的栈使用,而使用者不需要担心资源锁的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值