Skip to content

Latest commit

 

History

History
623 lines (335 loc) · 10 KB

File metadata and controls

623 lines (335 loc) · 10 KB

第2章 丰富的序列

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

  1. 列表推导式对可读性的影响
# 基于字符串构建一个Unicode码点列表
symbols = '$%^&*'
codes = []
for symbol in symbols:
    codes.append(ord(symbol))

codes    
[36, 37, 94, 38, 42]
# 使用列表推导式基于一个字符串构建一个Unicode码点列表
symbols = '$%^&*'
codes = [ord(symbol) for symbol in symbols]
codes
[36, 37, 94, 38, 42]

上述示例中,列表推导式更容易理解,如果超过两行,最好把语句拆开,或者使用传统的for循环重写。

  1. 列表推导式与map和filter比较
# 使用列表推导式和map/filter组合构建同一个列表
symbols = '$%^&*'
beyond_ascii = [ord(s) for s in symbols if ord(s) > 36]
beyond_ascii
[37, 94, 38, 42]
beyond_ascii = list(filter(lambda c: c > 36, map(ord, symbols)))
beyond_ascii
[37, 94, 38, 42]
  1. 笛卡尔积
# 使用列表推导式计算笛卡尔积
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
tshirts = [(color, size) for color in colors for size in sizes]
tshirts
[('black', 'S'),
 ('black', 'M'),
 ('black', 'L'),
 ('white', 'S'),
 ('white', 'M'),
 ('white', 'L')]
for color in colors:
    for size in sizes:
        print((color, size))
('black', 'S')
('black', 'M')
('black', 'L')
('white', 'S')
('white', 'M')
('white', 'L')
tshirts = [(color, size) for size in sizes
                         for color in colors]
tshirts
[('black', 'S'),
 ('white', 'S'),
 ('black', 'M'),
 ('white', 'M'),
 ('black', 'L'),
 ('white', 'L')]
  1. 生成器表达式
# 使用生成器表达式构建一个元组和数组
symbols = '$%^&*'
tuple(ord(symbol) for symbol in symbols)
(36, 37, 94, 38, 42)
import array

array.array('I', (ord(symbol) for symbol in symbols))
array('I', [36, 37, 94, 38, 42])
# 使用生成器表达式计算笛卡尔积
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
for tshirt in (f'{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种T恤衫组合的列表。

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

  1. 把元组当作记录使用
lax_coordinates = (33.9425, -118.408056)
city, year, pop, chg, area = ('Tokyo', 2003, 32_450, 0.66, 8014)
traveler_ids = [('USA', '31195855'), ('BRA', 'CE243567'), ('ESP', 'XDA205856')]

for passport in sorted(traveler_ids):
    print('%s/%s' % passport)
BRA/CE243567
ESP/XDA205856
USA/31195855
for country, _ in traveler_ids:
    print(country)
USA
BRA
ESP
  1. 并行赋值
lax_coordinates = (33.9425, -118.408056)
latitude, longitude = lax_coordinates
latitude, longitude
(33.9425, -118.408056)
  1. 使用*获取余下的项

并行赋值时,*前缀只能应用到一个变量上,可以是任何位置上的变量。

a, b, *rest = range(5)
a, b, rest
(0, 1, [2, 3, 4])
a, *body, c, d = range(5)
a, body, c, d
(0, [1, 2], 3, 4)
  1. 嵌套拆包
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(f'{"":15} | {"latitude":>9} | {"longitude":>9}')
for name, _, _, (lat, lon) in metro_areas:
    if lon <= 0:
        print(f'{name:15} | {lat:9.4f} | {lon:9.4f}')
                |  latitude | longitude
Mexico City     |   19.4333 |  -99.1333
New York-Newark |   40.8086 |  -74.0204
Sao Paulo       |  -23.5478 |  -46.6358

2.3 序列模式匹配

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(f'{"":15} | {"latitude":>9} | {"longitude":>9}')
for record in metro_areas:
    match record:
        case [name, _, _, (lat, lon)] if lon <= 0:
             print(f'{name:15} | {lat:9.4f} | {lon:9.4f}')
                |  latitude | longitude
Mexico City     |   19.4333 |  -99.1333
New York-Newark |   40.8086 |  -74.0204
Sao Paulo       |  -23.5478 |  -46.6358

匹配序列模式的条件:

  1. 匹配对象是序列。
  2. 匹配对象和模式的项数相等。
  3. 对应的项相互匹配,包括嵌套的项。

2.4 切片

# 从纯文本形式的发票中提取商品信息
invoice = """
0.....6.................................40........52...55........
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            

2.5 当列表不适用时

2.5.1 数组

如果一个列表只包含数值,使用array.array会更高效。

from array import array
from random import random
# 创建一个双精度浮点数数组
floats = array('d', (random() for i in range(10**7)))
floats[-1]
0.4197427599509004
# 把数组存入一个二进制文件中
fp = open('./data/floats.bin', 'wb')
floats.tofile(fp)
fp.close()
# 从二进制文件中读取1000万个数
floats2 = array('d')
fp = open('./data/floats.bin', 'rb')
floats2.fromfile(fp, 10**7)
fp.close()
floats2[-1]
0.4197427599509004
floats2 == floats
True

2.5.2 memoryview

内置的menoryview类是一种共享内存的序列类型,可在不复制字节的情况下处理数组的切片。

  • 分别以1x6、2x3和3x2矩阵的视图处理6字节内存
from array import array

octets = array('B', range(6))
m1 = memoryview(octets)
m1.tolist()
[0, 1, 2, 3, 4, 5]
# 构建2x3的矩阵
m2 = m1.cast('B', [2,3])
m2.tolist()
[[0, 1, 2], [3, 4, 5]]
# 构建3x2的矩阵
m3 = m1.cast('B', [3, 2])
m3.tolist()
[[0, 1], [2, 3], [4, 5]]
# octets、m1、m2、m3之间的内存是共享的
m2[1,1] = 22
m3[1,1] = 33
octets
array('B', [0, 1, 2, 33, 22, 5])
  • 修改一个16位整数数组中某一项的字节,改变该项的值
numbers = array('h', [-2, -1, 0, 1, 2])
memv = memoryview(numbers)
len(memv)
5
memv[0]
-2
memv_oct = memv.cast('B')
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])

2.5.3 双端队列和其他队列

  • deque类实现一种线程安全的双端队列,旨在快速在两端插入和删除项。
  • queue提供几个同步(即线程安全)队列类,在队列填满后,阻塞插入新项,等待其他线程从队列中取出一项。
  • multiprocessiong单独实现了无界SimpleQueue和有界的Queue。
  • asyncio提供了Queue、LifoQueue、PriorityQueue和JoinableQueue,为管理异步编程任务而做了修改。
# 处理一个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)
# 轮转。当n>0时,从右端取几项放到左端,当n<0时,从左端取几项放到右端。
dq.rotate(3)
print(dq)
dq.rotate(-4)
print(dq)
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
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)

2.6 杂谈

  • 元组的本质:ABC语言的compound类型是Python元组的前身。该类型还支持并行复制,也可用作字典的组合键。但是不是序列,不可迭代,不饿能通过索引获取字段,不能使用切片。

  • 扁平序列与容器序列:

    • 扁平序列:是不可嵌套的序列类型,只能包含简单的原子类型,例如整数、浮点数或字符。
    • 容器序列:可以嵌套,包含任何类型的对象,甚至是容器类型自身。
  • 大杂烩列表:

    • 如果列表中的项不可比较,就不能排序。
    • 如果元组中的每一项都是一个字段,那么每个字段就可以具有不同的类型。
  • 聪明的key

    • 只需要定义一个单参数函数,通过它获取或计算用于排序对象的标准即可。
    • 高效:key指定的函数只在处理各项时调用一次,而双参数比较函数在每次需要通过排序算法比较两项时,都需要调用一次。
  • 甲骨文、谷歌和Timbot阴谋论:

    • Oraclelist.sort使用的排序算法是Timsort,一种自适应算法,可根据数据的排序方法在插入排序法和归并排序法之间切换。
    • Timsort于2002年首次在CPython使用。
    • Timsort由Tim Peters发明,称为Timbot