文章目录
概述
本章讨论的内容几乎可以应用到所有的序列类型上,从我们熟悉的list,到 Python 3 中特有的 str 和 bytes。我还会特别提到跟列表、元组、数组以及队列有关的话题。
序列
- 最重要也最基础的序列类型应该就是列表(list)了。list 是一个可变序列,并且能同时存放不同类型的元素。我想你应该对它很了解了,因此让我们直接开始讨论列表推导(listcomprehension)吧。列表推导是一种构建列表的方法,它异常强大,然而由于相关的句法比较晦涩,人们往往不愿意去用它。掌握列表推导还可以为我们打开生成器表达式(generator expression)的大门,后者具有生成各种类型的元素并用它们来填充序列的功能。下面就来看看这两个概念
列表推导和生成器表达式
- 列表推导是构建列表(list)的快捷方式,而生成器表达式则可以用来创建其他任何类型的序列。如果你的代码里并不经常使用它们,那么很可能你错过了许多写出可读性更好且更高效的代码的机会。
列表推导和可读性
"""
列表推导式使用原则:只用列表推导式创建新的列表,并且尽量保持简短
如果列表推导式的代码超过了两行,建议使用for循环重写
"""
# 2_1 普通实现
def str_to_unicode1(symbols: str) -> list:
"""把一个字符串变成unicode码位的列表"""
codes = []
for symbol in symbols:
codes.append(ord(symbol))
return codes
# 2_1 列表推导式实现
def str_to_unicode2(symbols: str) -> list:
"""把一个字符串变成unicode码位的列表"""
return [ord(symbol) for symbol in symbols]
if __name__ == '__main__':
a = ")(@#$"
unicode_a1 = str_to_unicode1(a)
unicode_a2 = str_to_unicode2(a)
print(unicode_a1) # [41, 40, 64, 35, 36]
print(unicode_a2) # [41, 40, 64, 35, 36]
列表推导同filter和map的比较
- filter 和 map 合起来能做的事情,列表推导也可以做,而且还不需要借助难以理解和阅读的 lambda 表达式
"""
map(function, iterable, ...): 据提供的函数对指定序列做映射。Python 2.x 返回列表,Python 3.x 返回迭代器。
filter(function, iterable):用于过滤序列,过滤掉不符合条件的元素。Python2.7 返回列表,Python3.x 返回迭代器
"""
if __name__ == '__main__':
symbols = "&¥)(@#$~——"
# 列表推导式
beyond_ascii1 = [ord(s) for s in symbols if ord(s) > 127]
# filter + map
beyond_ascii2 = list(filter(lambda c: c > 127, map(ord, symbols)))
print(beyond_ascii1) # [65509, 8212, 8212]
print(beyond_ascii2) # [65509, 8212, 8212]
笛卡尔积
- 使用列表推导可以生成两个或以上的可迭代类型的笛卡儿积
"""
使用列表计算笛卡尔积
"""
if __name__ == '__main__':
colors = ["black", "white"]
sizes = ["S", "M", "L"]
t = [(color, size) for color in colors for size in sizes]
for e in t:
print(e)
# ('black', 'S')
# ('black', 'M')
# ('black', 'L')
# ('white', 'S')
# ('white', 'M')
# ('white', 'L')
- 列表推导的作用只有一个:生成列表。如果想生成其他类型的序列,生成器表达式就派上了用场。
生成器表达式
'''
虽然可以用列表推导来初始化元组、数组或其他序列类型,但是生成器表达式是更好的选择。
这是因为生成器表达式背后遵守了迭代器协议,可以逐个产出元素,而不是先建立一个完整的列表,然后再把这个列表传递到某个函数里。
生成器表达式的语法和列表推导式差不多,只不过把方括号换成了圆括号。
如果生成器表达式是一个函数调用过程中的唯一参数,那么不需要额外再用括号把它围起来
'''
def generator_expression_tuple(symbols):
"""
generator_expression_tuple(symbols:str) -> tuple
生成器表达式生成元组
"""
return tuple(ord(symbol) for symbol in symbols)
def generator_expression_array(symbols):
"""
generator_expression_array(symbols:str) -> array
生成器表达式生成数组
"""
return array.array('I', (ord(symbol) for symbol in symbols))
if __name__ == '__main__':
symbols = "$¢£¥€¤"
t = generator_expression_tuple(symbols)
print(t) # (36, 162, 163, 165, 8364, 164)
a = generator_expression_array(symbols)
print(a) # array('I', [36, 162, 163, 165, 8364, 164])
元组不仅仅是不可变的列表
- 除了用作不可变的列表,它还可以用于没有字段名的记录
把元组用作记录
import os
if __name__ == '__main__':
# 洛杉矶经纬度
lax_coordinates = (33.9425, -118.6465774)
# 东京市的一些信息:市名、年份、人口(单位:百万)、人口变化(单位:百分比)和面积(单位:平方千米)
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)
# for循环可以分别提取元组中的元素,也叫拆包(unpacking)。
# 如果元组中第二个元素无需使用,可以使用"_"占位符进行赋值
for country, _ in traveler_ids:
print(country)
元组拆包
- 最好辨认的元组拆包形式就是平行赋值,也就是说把一个可迭代对象里的元素,一并赋值到由对应的变量组成的元组中
latitude, longitude = (33.9425, -118.408056)
- 另外一个很优雅的写法当属不使用中间变量交换两个变量的值:
b, a = a, b
- 还可以用 * 运算符把一个可迭代对象拆开作为函数的参数:
if __name__ == '__main__':
d1 = divmod(20, 8)
print(d1) # (2, 4)
t = (20, 8)
quotient, remainder = divmod(*t)
print((quotient, remainder)) # (2, 4)
- 函数可以用元组的形式返回多个值,然后调用函数的代码就能轻松地接受这些返回值:
if __name__ == '__main__':
# 函数使用元组的形式返回多个值,无用数据使用'_'占位符接收
_, filename = os.path.split('/home/luciano/.ssh/idrsa.pub')
print(filename) # idrsa.pub
- 使用 * 处理部分元素: 在平行赋值中,* 前缀只能用在一个变量名前面,但是这个变量可以出现在赋值表达式的任意位置
if __name__ == '__main__':
a, b, *rest = range(5)
print(rest) # [2, 3, 4]
m, *body, n = range(5)
print(body) # [1, 2, 3]
嵌套元组拆包
- 接受表达式的元组可以是嵌套式的,例如 (a, b, (c, d))。只要这个接受元组的嵌套结构符合表达式本身的嵌套结构,Python 就可以作出正确的对应
"""
使用嵌套元组获取城市经纬度
"""
if __name__ == '__main__':
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)),
]
fmt = '{:15} | {:9.4f} | {:9.4f}'
for name, cc, pop, (latitude, longitude) in metro_areas:
# 经度小于0(西半球)
if longitude < 0:
print(fmt.format(name, latitude, longitude))
# Mexico City | 19.4333 | -99.1333
# New York-Newark | 40.8086 | -74.0204
# Sao Paulo | -23.5478 | -46.6358
具名元组
- collections.namedtuple 是一个工厂函数,它可以用来构建一个带字段名的元组和一个有名字的类。namedtuple 构建的类的实例所消耗的内存跟元组是一样的,因为字段名都被存在对应的类里面。
from collections import namedtuple
if __name__ == '__main__':
City = namedtuple("City", "name country population coordinates")
tokyo = City("Tokyo", "JP", "36.933", (35.689722, 139.691667))
print(tokyo) # City(name='Tokyo', country='JP', population='36.933', coordinates=(35.689722, 139.691667))
# _fields 属性是一个包含这个类所有字段名称的元组
print(City._fields) # ('name', 'country', 'population', 'coordinates')
# _make() 通过接受一个可迭代对象来生成这个类的一个实例,它
# 的作用跟 City(*delhi_data) 是一样的
delhi_data = ('Delhi NCR', 'IN', 21.935, (28.613889, 77.20))
delhi1 = City._make(delhi_data)
delhi2 = City(*delhi_data)
# _asdict() 把具名元组以 collections.OrderedDict 的形式返回
asdict = delhi1._asdict()
print(asdict) # {'name': 'Delhi NCR', 'country': 'IN', 'population': 21.935, 'coordinates': (28.613889, 77.2)}
for k, v in delhi1._asdict().items():
print(k + ":", v)
# name: Delhi NCR
# country: IN
# population: 21.935
# coordinates: (28.613889, 77.2)
切片
- 在 Python 里,像列表(list)、元组(tuple)和字符串(str)这类序列类型都支持切片操作,但是实际上切片操作比人们所想象的要强大很多。
- 在切片和区间操作里不包含区间范围的最后一个元素是 Python 的风格。
对对象进行切片
- 可以用
s[a:b:c]
的形式对 s 在 a 和b 之间以 c 为间隔取值
if __name__ == '__main__':
s = 'bicycle'
print(s[::3]) # bye
print(s[::-1]) # elcycib
print(s[::-2]) # eccb
给切片赋值
- 如果把切片放在赋值语句的左边,或把它作为 del 操作的对象,我们就可以对序列进行嫁接、切除或就地修改操作
'''
如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代对象。
即便只有单独一个值,也要把它转换成可迭代的序列
'''
if __name__ == '__main__':
a = list(range(10))
a[2:5] = [20, 30]
print(a) # [0, 1, 20, 30, 5, 6, 7, 8, 9]
del a[5:7]
print(a) # [0, 1, 20, 30, 5, 8, 9]
a[3::2] = [11, 22]
print(a) # [0, 1, 20, 11, 5, 22, 9]
# a[2:5] = 100 # TypeError: must assign iterable to extended slice
a[2:5] = [100]
print(a) # [0, 1, 100, 22, 9]