提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
《流畅的Python》学习笔记
提示:以下是本篇文章正文内容,下面案例可供参考
列表推导是构建列表(list)的快捷方式;生成器表达式可以创建其它任何类型的序列。
列表推导示例:
>>> symbols = '$¢¤'
>>> codes = [ord(symbol) for symbol in symbols]
...
>>> codes
[65284, 65504, 164]
使用列表推导原则:只用列表推导来创建新的列表,并且尽量保持简短。(超过两行可考虑是否使用列表推导)
ord()
:返回对应的Ascii数值,或者Unicode数值
生成器表达式背后遵守了迭代器协议可以逐个地产出元素,而不是先建立一个完整的列表,然后再把这个列表传递到某个构造函数里。
1.用生成器表达式初始化元组和数组:
>>> symbols = '$¢¤'
>>> tuple(ord(symbol) for symbol in symbols)
(65284, 65504, 164)
>>> import array
>>> array.array('I', (ord(symbol) for symbol in symbols))
array('I', [65284, 65504, 164])
2.用生成器表达式计算笛卡尔积:
>>> colors = ['black', 'white']
>>> sizes = ['S','M','L']
>>> for tshirt in ('%s %s' % (c,s) for c in colors for s in sizes):
>>> print(tshirt)
...
black S
black M
black L
white S
white M
white L
使用生成器表达式后,生成器表达式逐个产出元素,内存不会留下一个6个组合的列表,因为生成器表达式会在每次for循环运行时才生成一个组合
元组拆包:将元组中的数值分别赋值给多个变量。如:
>>> city, year, pop, area = ('Tokyo', 2003, 32450, 8014) #元组拆包
平行赋值:把一个可迭代对象里的元素,一并复制到由对应的变量组成的元组中。
>>> city, year, pop, area = ('Tokyo', 2003, 32450, 8014) #元组拆包
>>> b,a = a,b
*
运算符把一个可迭代对象拆开作为函数参数 >>> divmod(20,8)
(2,4)
>>> t = (20,8)
>>> divmod(*t)
(2,4)
使用*
运算符获取不确定的参数
>>> a,b,*rest = range(5)
>>> a,b,rest
(0,1,[2,3,4])
小知识
*
前缀只能用在一个变量名前面,但是这个变量可以出现在赋值表达式的任意位置。
让一个函数以元组的方式返回多个值。
>>> import os
>>> _, filename = os.path.split('/home/luciano/.ssh/idrsa.pub')
>>> filename
'idrsa.pub'
os.path.splite()
: 返回以路径和最后一个文件名组成的元组(path, last_part)。
_
: 在不需要输出元组中的某个值时,可以使用占位符。
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}' #9.4f表示一共占9位,小数占4位
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(class_name, zd_name)
:构建一个带字段名的元组和一个有名字的类。该函数有两个参数:
class_name:类名
zd_name:类的各字段的名字。
# 定义和使用具名元组
from collections import namedtuple
City = namedtuple('City', 'name country population coordinates')
tokyo = City('Tokyo', 'JP', 36.933,(35.689722,139.691667))
print(tokyo)
print(tokyo.population) ①
print(tokyo[1]) ②
输出:
City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, 139.691667))
36.933
JP
输出字段可以用实例名.具体字段名
表示,如①,也可以使用实例名[i]
这种数组形式表示,如上图中的②
# 具名元组的属性和方法
>>> 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() ③
{'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()
:接受一个可迭代对象生成这个类的一个实例,作用和City(*delhi_data)
相同
_asdict()
:把具名元组以collections.OrderedDict
的形式返回
可采用s[a:b:c]
的形式对s在a和b之间以c为间隔取值。若c<0
则表示反向取值。
>>> s = 'bicycle'
>>> s[::3]
'bye'
>>> s[::-1]
'elcycib'
>>> s[::-2]
'eccb'
(start, stop)
都可见时,区间长度=stop-start
。my_list[:x]
和my_list[x:]
即可。>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5]=[20,30] #将l中的第2到4的数据替换成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 <module>
TypeError: can only assign an iterable
>>> l[2:5]=[100]
>>> l
[0, 1, 100, 22, 9]
例1:
>>> board = [['_']*3 for i in range(3)]
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][2] = 'X'
>>> board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
*
:Python中可以使用*
把一个序列复制几份再拼接起来
例2:
>>> weird_board = [['_'] * 3] * 3
>>> weird_board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> weird_board[1][2] = '0'
>>> weird_board
[['_', '_', '0'], ['_', '_', '0'], ['_', '_', '0']]
比较这两个例子可以发现,在例1中对board[1][2]进行赋值,只影响了一个元素,而例2中,同样是对weird_board[1][2]赋值,但可以看见weird_board[0][2],weird_board[1][2],weird_board[2][2]的值都被影响了。
原因:例2的列表其实是包含3个指向同一列表的引用
“ += ”背后的特殊方法是__iadd__
(用于“就地加法”)。但如果一个类没有实现这个方法,Python则会调用__add__
。
>>> t = (1, 2, [30, 40])
>>> t[2] += [50, 60]
Traceback (most recent call last):
File "" , line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])
可以看见,程序即完成了在后面添加值的过程,也报出了异常。它的执行过程如下图所示:
有以下几点需要注意:
*=
背后的特殊方法是__imul__
。
在可变序列(列表)和不可变序列(元组)中,该符号作用不同:
# 可变序列
>>> l *= 2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l)
2043293161600
# 不可变序列
>>> t = (1, 2, 3)
>>> id(t)
2043292853312
>>> t *= 2
>>> id(t)
2043292317344
>>> t
(1, 2, 3, 1, 2, 3)
可以看见,在列表中运用*=
后,列表的ID不变,新的元素追加到列表上;在元组中运用*=
后,元组的ID变化,新的元组被创建。解释器会先把原来对象中的元素先复制到新的对象里,然后再追加新的元素。
小知识
str是一个例外。程序在为str初始化内存时,会为它流出额外的可扩展空间,因此进行增量操作时,并不会涉及复制原有字符串到新位置这类操作。
区别:
list.sort
:就地排序列表,即不会把原列表复制一份。如果一个函数或者方法对对象进行的是就地改动,那它就应该返回None,好让调用者知道传入的参数发生了变动,并且未产生新的对象。
sorted
:此为内置函数,它会创建一个新的列表作为返回值。该方法接受任何形式的可迭代对象作为参数,最后返回一个列表。
相同点:
二者都有两个可选的关键字参数:
reverse
:默认值为False,当值为True时,序列元素按字母降序输出。
key
:一个只有一个参数的函数,这个函数会被用在序列里的每一个元素上,所产生的结果将是排序算法依赖的对比关键字。默认用元素自己的值来排序。
key=len
:基于字符串长度升序排序
key=lower
:忽略大小写排序
>>> fruits = ['grape', 'raspberry', 'apple', 'banana']
# 使用sorted排序
>>> sorted(fruits)
['apple', 'banana', 'grape', 'raspberry']
>>> fruits #原列表没有变换
['grape', 'raspberry', 'apple', 'banana']
>>> sorted(fruits, reverse=True) #按照字母降序排序
['raspberry', 'grape', 'banana', 'apple']
>>> sorted(fruits, key=len) #按照元素长度升序排序
['grape', 'apple', 'banana', 'raspberry']
>>> sorted(fruits, key=len, reverse=True) #按照长度降序排列,长度一样时,grape和apple的相对位置不会改变
['raspberry', 'banana', 'grape', 'apple']
>>> fruits
['grape', 'raspberry', 'apple', 'banana']
# 使用.sort()排序
>>> fruits.sort() #原列表变换
>>> fruits
['apple', 'banana', 'grape', 'raspberry']
模块包含两个函数:bisect
和insort
,二者都用二分查找法来在有序列表中查找或插入元素。
bisect(haystack,needle)
:在haystack里搜索needle的位置,该位置满足的条件是,把needle插入这个位置之后,haystack还能保持升序。其中,haystack必须是一个有序的序列。
例:在有序序列中使用bisect查找某个元素的插入位置
import bisect
import sys
HAYSTACK = [1,4,5,6,8,12,15,20,21,23,23,26,29,30]
NEEDLE = [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(NEEDLE):
position = bisect_fn(HAYSTACK,needle) # 利用特定的bisect函数计算元素应该出现的位置
offset = position * ' |' # 利用该位置计算需要几个分隔符
print(ROW_FMT.format(needle, position, offset)) # 把元素及其应该出现的位置打印出来
if __name__=='__main__':
if sys.argv[-1] == 'left': # 根据命令上最后一个参数来选用bisect函数
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)
执行结果:
DEMO: bisect_right
haystack -> 1 4 5 6 8 12 15 20 21 23 23 26 29 30
31 @ 14 | | | | | | | | | | | | | |31
30 @ 14 | | | | | | | | | | | | | |30
29 @ 13 | | | | | | | | | | | | |29
23 @ 11 | | | | | | | | | | |23
22 @ 9 | | | | | | | | |22
10 @ 5 | | | | |10
8 @ 5 | | | | |8
5 @ 3 | | |5
2 @ 1 |2
1 @ 1 |1
0 @ 0 0
bisect的两个可选参数:
lo: 默认值为0
hi: 默认值是序列长度
可用这两个参数来缩小搜寻的范围
bisect_left和bisect_right:
利用bisect函数建立一个用数字作为索引的查询表格:
def grade(score, breakpoints=[60,70,80,90], grade='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函数得到分数在breakpoint的哪个位置上,再利用该位置,得出相应的等级。
insort(seq, item)
:把变量item插入到序列seq中,并保持seq的升序序列。
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)
-----------------------------------------------------
10 -> [10]
0 -> [0, 10]
6 -> [0, 6, 10]
8 -> [0, 6, 8, 10]
7 -> [0, 6, 7, 8, 10]
2 -> [0, 2, 6, 7, 8, 10]
10 -> [0, 2, 6, 7, 8, 10, 10]
insort
也可使用lo和hi两个可选参数控制查找范围。他也有个变体为insert_left
,变体背后使用的是bisect_left
。
如:
创建数组需要一个类型码,这个类型码用来表示在底层的C语言应该存放怎样的数据类型。
Type code | C Type | Minimum size in bytes |
---|---|---|
‘b’ | signed integer | 1 |
‘B’ | unsigned integer | 1 |
‘u’ | Unicode character | 2 |
‘h’ | signed integer | 2 |
‘H’ | unsigned integer | 2 |
‘i’ | signed integer | 2 |
‘I’ | unsigned integer | 2 |
‘l’ | signed integer | 4 |
‘L’ | unsigned integer | 4 |
‘q’ | signed integer | 8 |
‘Q’ | unsigned integer | 8 |
‘f’ | floating point | 4 |
‘d’ | floating point | 8 |
# 一个浮点数组的创建、存入文件和从文件读取的过程
>>> from array import array
>>> from random import random
>>> floats = array('d', (random() for i in range(10**7))) #利用一个可迭代对象建立一个双精度浮点数组(类型码为'd')
>>> floats[-1] # 查看数组的最后一个元素
0.20972132845765767
>>> fp = open('floats.bin', 'wb')
>>> floats.tofile(fp) # 把数组存入一个二进制文件里
>>> fp.close()
>>> floats2 = array('d') # 新建一个双精度浮点空数组
>>> fp = open('floats.bin', 'rb')
>>> floats2.fromfile(fp, 10**7) # 把100万个浮点数从二进制文件里读取出来
>>> fp.close()
>>> floats2[-1]
0.20972132845765767
>>> floats2 == floats
True
array.tofile(f)
:把数组中所有的元素以机器值的形式写入一个文件farray.fromfile(f, n)
:将二进制文件f内含有机器值读出来添加到尾部,最多添加n项小知识
从Python3.4开始,数组类型不再支持诸如list.sort()
这种就地排序方法。要给数组排序的话,得用sorted
函数新建一个数组:
a = array.array(a.typecode, sorted(a))
memoryview是一个内置类,它能让用户在不复制内容的情况下操作同一个数组的不同切片。
memoryview.cast
:能用不同的方法读写同一块内存数据,而且内容字节不会随意移动。
# 通过改变数组中的一个字节来更新数组里的某个元素的值
>>> import array
>>> numbers = array.array('h', [-2,-1,0,1,2]) # 利用含有5个短整型有符号整数的数组创建一个memoryview
>>> memv = memoryview(numbers)
>>> len(memv)
5
>>> memv[0] # memoryview里的五个元素和数组里的没有区别
-2
>>> memv_oct = memv.cast('B') # 把memv里的内容转为'B'类型,即无符号字符
>>> memv_oct.tolist() # 以列表形式查看memv_oct的内容
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
>>> memv_oct[5] = 4 # 把位于位置5的字节赋值为4
>>> numbers
array('h', [-2, -1, 1024, 1, 2])
把占两个字节的整数的高位字节改成了4,原来0是表示为 0000 0000 0000 0000,高位改为4之后,变为:0000 0000 0010 0000,按照从右往左看,该值即为1024。
**NumPy:**实现了多为同质数组和矩阵,这些数据结构不但能够处理数字,还能存放其他由用户定义的记录。通过NumPy,用户能对这些 数据结构里的元素进行高效地操作。
**SciPy:**基于NumPy的另一个库,它提供了很多跟科学计算有关的算法,专为线性代数、数值积分和统计学而设计。
# 对numpy.ndarray的行列进行基本操作
>>> import numpy
>>> a = numpy.arange(12)
>>> a
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> type(a)
<class 'numpy.ndarray'>
>>> a.shape # 查看数组维度,显示它是一维的,有12个元素的数组
(12,)
>>> a.shape = 3,4 # 把数组变成3行4列的二维数组
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> a[2]
array([ 8, 9, 10, 11])
>>> a[2,1]
9
>>> a[:,1]
array([1, 5, 9])
>>> a.transpose() # 交换数组的行和列
array([[ 0, 4, 8],
[ 1, 5, 9],
[ 2, 6, 10],
[ 3, 7, 11]])
# 对numpy.ndarray中的元素进行抽象的读取、保存和其他操作
>>> import numpy
>>> floats = numpy.loadtxt('floats-10M-lines.txt') # 从文本文件中读取100万个浮点数
>>> floats[-3:] # 利用序列切片来读取其中的最后三个数
array([3016362.69195522, 535281.10514262, 4566560.44373946])
>>> floats *= .5 # 将数组里的数都诚意0.5
>>> floats[-3:]
array([1508181.34597761, 267640.55257131, 2283280.22186973])
>>> from time import perf_counter as pc # 导入精度和性能都比较高的计时器
>>> t0 = pc(); floats /= 3; pc() - t0 # 计算将100万个数都除以3所耗费的时间
0.03690556308299495
>>> numpy.save('floats-10M',floats) # 把数组存入后缀为.npy的二进制文件
>>> floats2 = numpy.load('floats-10M.npy','r+') # 将上面的数据导入另一个数组里,这次load方法使用内存映射的机制
>>> floats2 *= 6
>>> floats2[-3:]
memmap([3016362.69195522, 535281.10514262, 4566560.44373946])
内存映射:使得在内存不足的情况下仍然可以对数组做切片
collections.deque
类(双向队列)是一个线程安全、可以快速从两端添加或者删除元素的数据类型。如果想要有一种数据类型来存放“最近用到的几个元素”,deque是一个很好的选择。
>>> from collections import deque
>>> dq = deque(range(10), maxlen=10) # maxlen是一个可选参数,代表队列的容纳元素数量,一旦设定就不能更改
>>> 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)
.rotate(n)
:队列的旋转操作接受一个参数n,
.extendleft(iter)
:会把迭代器里的元素逐个添加到双向队列的左边,因此迭代器里的元素会逆序出现在队列里
小知识
队列中的append
和popleft
都是原子操作,也就是说deque可以再多线程程序中安全地当做先进先出的队列使用,而使用者不需要担心资源锁的问题。
Python中的其他队列:
queue
提供了同步类 Queue
、LifoQueue
和PriorityQueue
,不同的线程可以利用这些数据类型来交换信息。
三者都有可选参数maxsize
,接受整数来限定队列大小
在队列满时,不会扔掉旧元素,而是被锁住,直到另外的线程溢出了某个元素而腾出了位置
很适合用来控制活跃线程的数量
multiprocessing
实现了自己的Queue
,用于进程间通信
含有multiprocessng.JoinableQueue
类型,可以使管理任务变得更方便
asyncio
Python3.4提供的包,包含Queue
、LifoQueue
、PriorityQueue
、JoinableQueue
,为异步编程里的任务管理器提供了遍历
heapq
与上面三个模块不同,heapq没有队列类,而是提供了heappush
和heappop
方法,让用户可以把可变序列当做堆序列或者优先序列来使用