python中丰富的序列

目录

一、列表推导式和生成器表达式

1 列表推导式

1.1 对可读性的影响

1.2 应用

1.3 与map、filter比较

2 生成式表达式

3 局部作用域

二、元组的两种用法

三、拆包

1 并行赋值

2 变量值对调

3 序列字面量中使用*拆包

4 函数调用

5 嵌套拆包

四、切片

1 切片和区间排除最后一页

2 切片对象

3 省略号

4 为切片赋值

五、使用+和*处理序列

1 构建嵌套列表

2 增值运算符处理序列

六、排序函数list.sort与sorted


一、列表推导式和生成器表达式

列表推导式(目标是列表)和生成器表达式(目标是其他序列类型)都可以快速构建一个序列。

1 列表推导式

1.1 对可读性的影响

举例说明利用列表推导式写的代码更易于理解、意图明确

# 基于一个字符串构建一个Unicode码点列表
symbols = '@#$%^&*'
codes = []
for symbol in symbols:
    codes.append(ord(symbol))
print(codes)
# output: [64, 35, 36, 37, 94, 38, 42]

# 使用列表推导式基于一个字符串构建一个Unicode码点列表
codes = [ord(symbol) for symbol in symbols]
print(codes)
# output: [64, 35, 36, 37, 94, 38, 42]

注意:滥用列表推导式写出的代码也不一定易于理解,如果并不打算使用生成的列表,那就不要使用列表推导式句式。

1.2 应用

使用列表推导式构建一个列表,其中包含两个或更多列表中的所有项组成的元组。

举例:使用列表推导式计算笛卡尔积

# 生成一个列表,其中包含2种颜色和3种尺寸
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
# 先按颜色再按尺寸排列
tshirts = [(color, size) for color in colors
                         for size in sizes]
print(tshirts)
# output: [('black', 'S'), ('black', 'M'), ('black', 'L'),
#           ('white', 'S'), ('white', 'M'), ('white', 'L')]
for color in colors:
    for size in sizes:
        print((color, size))
# output:
# ('black', 'S')
# ('black', 'M')
# ...

# 先按尺寸再按颜色排列
tshirts = [(color, size) for size in sizes
                         for color in colors]
print(tshirts)
# output:[('black', 'S'), ('white', 'S'), ('black', 'M'),
#          ('white', 'M'), ('black', 'L'), ('white', 'L')]

1.3 与map、filter比较

 列表表达式涵盖了map和filter两个函数的功能,相比lambda表达式更易理解

补充:map函数和filter函数的用法

1) filter(function,iterable):返回一个迭代器,接收两个参数,第一个为函数,第二个为序列。序列的每个元素作为参数传递给函数进行判断,函数返回True/False,最后将返回True的元素放入新列表。

2) map(function,iterable,...):返回一个迭代器,可以传入一个或多个序列,第一个参数function以参数序列中的每一个元素调用function函数,返回包含每次function函数返回值的新列表

举例:使用列表推导式和map、filter组合构建同一个列表

symbols = '@#$%^&*'
beyond_ascii = [ord(s) for s in symbols if ord(s) < 50]
print(beyond_ascii)
# output: [35, 36, 37, 38, 42]

beyond_ascii = list(filter(lambda c: c < 50, map(ord, symbols)))
print(beyond_ascii)
# output: [35, 36, 37, 38, 42]

2 生成式表达式

如果需要生成其他类型的序列应该使用生成式表达式。虽然列表推导式也可以生成元组、数组或其他类型的序列,但生成器表达式占用内存更少,因为生成器表达式使用迭代器逐个产出项,而不是构建整个列表。

生成器表达式的句法与列表推导式几乎一样,只是将方括号换成圆括号了。

举例:使用生成器表达式构建一个元组和一个数组

symbols = '@#$%^&*'

print(tuple(ord(s) for s in symbols))
# output: (64, 35, 36, 37, 94, 38, 42)

import array
print(array.array('I', (ord(s) for s in symbols)))
# output: array('I', [64, 35, 36, 37, 94, 38, 42])

3 局部作用域

python3中的列表推导式、生成器表达式、以及类似的集合推导式、字典推导式,for子句中赋值的变量在局部作用域内。但是,使用海象运算符 := 赋值的变量,其作用域可以扩宽到函数内。

x = 'ABC'
codes = [ord(x) for x in x]
print(x)
# output: ABC
print(codes)
# output: [65, 66, 67]
codes = [last := ord(c) for c in x]
print(last)
# output: 67 ->last依然可以访问
print(c)
# output: NameError: name 'c' is not defined
#   ->c消失了,因为它只存在列表推导式的内部

二、元组的两种用法

python元组的两种用法

三、拆包

拆包的特点是不用自己动手通过索引从序列中提取元素,这样减少了出错的可能,拆包的目标可以是任何可迭代对象

1 并行赋值

最明显的拆包形式是并行赋值--把可迭代对象中的项赋值给变量元组

lax_coordinate = (33.9425, -118.408056)
latitude, longitude = lax_coordinate # 拆包
print(latitude)
# output: 33.9425
print(longitude)
# output: -118.408056

2 变量值对调

 利用拆包还可以轻松对调两个变量的值,省掉中间临时值

b, a = a, b

3 序列字面量中使用*拆包

print((*range(4), 4))
# output: (0, 1, 2, 3, 4)
print([*range(4), 4])
# output: [0, 1, 2, 3, 4]
print({*range(4), 4, *(5, 6, 7)})
# output: {0, 1, 2, 3, 4, 5, 6, 7}

4 函数调用

定义函数时可以使用*args捕获余下的任意数量的参数

def fun(a, b, c, d, *rest):
    return a, b, c, d, rest

print(fun(*[1, 2], 3, *range(4, 7)))
# output: (1, 2, 3, 4, (5, 6))

5 嵌套拆包

拆包的对象可以嵌套,如:(a, b, (c, d)),如果值的嵌套结构是相同,则python能正确处理。

四、切片

python中,列表、元组、字符串等所有序列类型都支持切片操作。

1 切片和区间排除最后一页

这是一种python分隔约定,这样做的好处有:

1) 当只指定停止位置时,容易判断切片或区间的长度,如:range(3)和my_list[:3]的长度均产生3项;

2)同时指定起始和停止位置时,容易计算长度,做减法即可:stop-start;

3)方便在索引x处把一个序列拆分成两部分而不产生重合,如:my_list[:x]和my_list[x:]

2 切片对象

我们可以使用s[a:b:c]来指定步距c,让切片跳过部分项,a:b:c表示法,只在[]内部有效,得到的结果是一个切片对象:slice(a, b, c)

使用:从纯文本形式的发票中提取商品信息

invoice = '''
0     6                                 40           52  55        
1909  pimoroni piBrlla                  $17.50       3   $52.50
1489  6mm Tactille 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_item = invoice.split('\n',)[2:]
for item in line_item:
    print(item[UNIT_PRICE], item[DESCRIPTION])
    # output: 
    # $17.50       pimoroni piBrlla                  
    # $4.95        6mm Tactille switch x20           
    # $28.00       Panavise Jr. -PV-201              
    # $34.95       piTFT Mini Kit 320x240            

3 省略号

省略号是一个内置常量,写作三个点(...),python解释器把它识别为一个记号。省略号是Ellipsis对象的别名,而Ellipsis对象是ellipsis类的单例。

print(type(...))
# output: <class 'ellipsis'>

使用:可以作为参数传给函数,也可以写在切片规范中,如:f(a, ..., z)或a[i:...]

4 为切片赋值

 在赋值语句左侧使用切片表示法,或作为del语句的目标,可以就地移植、切除、修改可变序列

>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5] = [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 "<input>", line 1, in <module>
TypeError: can only assign an iterable
>>> l[2:5] = [100]
>>> l
[0, 1, 100, 22, 9]

注意:如果赋值目标是一个切片,那么右边必须是一个可迭代对象,即使只有一项 

五、使用+和*处理序列

使用+和*实现拼接操作,其中+的运算对象必须是同一种序列。+和*始终创建一个新对象,绝不更改操作数

1 构建嵌套列表

一个列表中嵌套3个长度为3的列表,可以表示井字游戏棋盘

board = [['_'] * 3 for i in range(3)]
print(board)
# output: [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
board[1][2] = 'X'
print(board)
# output: [['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]

 注意:不能在一个列表中3次引用同一个列表

weird_board = [['_'] * 3] * 3
print(weird_board)
# output: [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
weird_board[1][2] = 'O'
print(weird_board)
# output: [['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]

在第2行第3列放‘O’是就会发现所有行引用的都是同一个对象

2 增值运算符处理序列

根据第一个操作数而定,增值运算符+=和*=的行为差异较大。

背后支持 += 运算符的是特殊方法__iadd__(就地相加),以简单的表达式来说明:

a += b

如果a实现了__iadd__,那就会调用它,如果a是可变序列,则就地修改a(行为类似a后面追加b);如果a没有实现__iadd__方法,那么表达式的作用就等同于a = a + b,先求解a + b,再把得到的新对象绑定到a上。因此a绑定的对象可能变了,也可能没变。

对于可变序列最好实现__iadd__方法,实现就地修改;对于不可变序列,显然不能就地修改。

以上内容也适用于通过__imul__实现的*=运算符。下面使用*=运算符分别处理一个可变序列和一个不可变序列:

>>> l = [1, 2, 3]
>>> id(l)
1285878263232
>>> l *= 2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l)        # 乘法运算之后,列表还是同一个对象
1285878263232
>>> t = (1, 2, 3)
>>> id(t)
1285878263424
>>> t *= 2
>>> t
(1, 2, 3, 1, 2, 3)
>>> id(t)        # 乘法运算之后,创建了一个新元组
1285878203584

 对不可变序列重复拼接效率低下,因为解释器必须复制整个目标序列,创建一个新序列,包含拼接的项,而不是简单追加新项。

注意:增量赋值不是原子操作

六、排序函数list.sort与sorted

list.sort方法就地排序列表,不创建副本,返回值为None。它更改了接收者,没有创建新列表。

内置函数sorted与之相反,返回创建的新列表。该函数接收任何可迭代对象作为参数。包括不可变序列和生成器。

list.sort和sorted均接收两个可选的关键字参数:

1)reverse:值为True时,降序返回项;默认值为False

2)key:一个值接受一个参数的函数,应用到每一项上,作为排序的依据。如:排序字符串时,key = len表示按字符长度排除各个字符串,key = str.lower表示执行不区分大小写的排序。默认为恒等函数。

举例:

>>> fruits = ['grade', 'raspberry', 'apple', 'banana']
>>> sorted(fruits)
['apple', 'banana', 'grade', 'raspberry']
>>> fruits        # 查看原来的列表发现并没有改动
['grade', 'raspberry', 'apple', 'banana']
>>> sorted(fruits, reverse=True)
['raspberry', 'grade', 'banana', 'apple']
>>> sorted(fruits, key=len)
['grade', 'apple', 'banana', 'raspberry']
>>> sorted(fruits, key=len, reverse=True)
['raspberry', 'banana', 'grade', 'apple']    #grade与apple的相对位置表明了排序算法很稳定
>>> fruits
['grade', 'raspberry', 'apple', 'banana']
>>> fruits.sort()        # 就地排序,返回None,被控制台省略了
>>> fruits        # 原来的列表有序了
['apple', 'banana', 'grade', 'raspberry']

这个例子还表明了Python的排序算法是稳定的(即能保留比较时相等两项的相对顺序)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值