全面深入了解python(二)
1. 序列构成的数组
1.1 内置序列类型概览
python标准库用C实现了丰富的序列类型:
容器序列:
list、tuple和collections.deque这些序列能存放不同类型的数据。
扁平序列:
str、bytes、bytearray、memoryview和array.array,这类序列只能存放一种类型的数据。
容器序列存放的是它们所包含的任意类型的对象的引用,而扁平序列里存放的是值而不是引用。换句话说,扁平序列其实是一段连续的内存空间。扁平序列更加紧凑,但是只能存放字符、字节和数值这种基础类型。
序列类型按照能否被修改分类:
可变序列:
list、bytearray、collections.deque、memoryview和array.array
不可变序列:
tuple、str和bytes
1.2 列表推导和生成器表达式
列表推导是构建列表的快捷方式,而生成器表达式则可以用来创建其他任何类型的序列。
1.2.1 列表推导和可读性
传统的构建列表
>>> pre_ord = '!@#$%^'
>>> codes = []
>>> for spec in pre_ord:
... codes.append(ord(spec))
...
>>> codes
[33, 64, 35, 36, 37, 94]
另外一种写法:
>>> pre_ord = '!@#$%^'
>>> codes = [ord(spec) for spec in pre_ord]
>>> codes
[33, 64, 35, 36, 37, 94]
显然第二种写法更为方便和具有可读性,如果列表推导的代码超过了2行,可以考虑使用for循环,没有必要强行使用列表推导式。
1.2.2 列表推导同filter和map的比较
filter和map合起来能做的事情,列表推导也可以做,而且还不需要借助lambda表达式。
>>> pre_ord = '!@#$%^'
>>> after_ord = [ord(s) for s in pre_ord if ord(s) < 50]
>>> after_ord
[33, 35, 36, 37]
>>> after_ord = list(filter(lambda c : c < 50,map(ord,pre_ord)))
>>> after_ord
[33, 35, 36, 37]
1.3 笛卡尔积
如果你需要一个列表,列表里是3种不同尺寸的外套,每种外套都有2种颜色。
>>> colors = ['black','white']
>>> sizes = ['S','M','L']
>>> coats = [(color,size) for color in colors for size in sizes]
>>> coats
[('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')]
如果这里你看不懂循环是如何嵌套的,这里用for循环再实现一遍:
>>> for color in colors:
... for size in sizes:
... print((color,size))
...
('black', 'S')
('black', 'M')
('black', 'L')
('white', 'S')
('white', 'M')
('white', 'L')
这样你就能很好理解了先后顺序了,这是先以颜色排列,再以尺码排列。如果想先按尺码后颜色的顺序排列,只要调换顺序就行了。
>>> coats = [(color,size) for size in sizes for color in colors]
>>> coats
[('black', 'S'), ('white', 'S'), ('black', 'M'), ('white', 'M'), ('black', 'L'), ('white', 'L')]
1.4 生成器表达式
生成器表达式的语法和列表推导差不多,只不过把方括号换成圆括号而已。
>>> pre_ord = '!@#$%^'
>>> tuple(ord(spec) for spec in pre_ord)
(33, 64, 35, 36, 37, 94)
>>> import array
>>> array.array('I',(ord(spec) for spec in pre_ord)) #第一个参数是数组存储类型
array('I', [33, 64, 35, 36, 37, 94])
>>> array.array('d',(ord(spec) for spec in pre_ord)) # 双精度
array('d', [33.0, 64.0, 35.0, 36.0, 37.0, 94.0])
上面的外套例子使用的是列表推导式,这里用生成器从新实现一次:
#python3.6
>>> for coat in (f'{c} {s}' for c in colors for s in sizes):
... print(coat)
...
black S
black M
black L
white S
white M
white L
#低于python3.6
>>> for coat in ('%s %s' % (c,s) for c in colors for s in sizes):
... print(coat)
...
black S
black M
black L
white S
white M
white L
和列表推导式比较起来,你会发现对内存的开销会减少,比如说在内存中不会留下一个6个组合的列表。如果2个列表分别有1000个元素呢?那就省掉一个含有100万个元素的列表。
2 .元组
刚接触python,就会碰到元组。对元组的最初印象只有一个,是“不可变列表”,其实元组是一种很强大的当作记录来用的数据类型,接下来会看到。
2.1 元组拆包
元组拆包可以应用到任何可迭代对象上,唯一的硬性要求是,拆分后的元素和对应的变量组成的元组的空档要一致。(除非用*来处理多余剩下的元素)
最好辨认的元组拆包形式就是平行赋值:
>>> coat = ('balck','L')
>>> color,size = coat
>>> color
'balck'
>>> size
'L'
还有最熟悉的变量交换:
>>> b, a = a, b
元组拆包还应用于让一个函数可以用元组的形势返回多个值,然后调用函数的代码就能轻松地接受这些返回值。以os.path.split()函数为例,会返回路径和最后一个文件名组成的元组(path,last_part):
>>> import os
>>> _,filename = os.path.split('/home/duke/mac_data/tuple.py')
>>> filename
'tuple.py'
这里使用_作为占位符
在前面提到了用*来处理剩下的元素,这样的好处是让我们可以更专注的处理我们需要的数据。
python中,函数用*args来获取不确定数量的参数是一种经典写法,因此,这个概念被扩展到平行赋值中:
>>> a,b,*rest = range(5)
>>> a,b,rest
(0, 1, [2, 3, 4])
>>> a,b,*rest = range(3)
>>> a,b,rest
(0, 1, [2])
>>> a,b,*rest = range(2)
>>> a,b,rest
(0, 1, [])
需要注意的是,*前缀只能用在一个变量前,但是可以放到任意位置,方便吧!
>>> a,*body,c,d = range(5)
>>> a,body,c,d
(0, [1, 2], 3, 4)
>>> *head,b,c,d = range(5)
>>> head,b,c,d
([0, 1], 2, 3, 4)
2.2 嵌套元组拆包
元组是可以嵌套的,例如(a,b,c,(d,e)),因此拆包形式符合嵌套结构,python就可以做出正确的响应。
>>> 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))
... ]
#python3.6
>>> for name,cc,pop,(latitude,longitude) in metro_areas:
... if longitude <= 0:
... print(f'{name:15} | {latitude:9.4f} | {longitude:9.4f}')
...
Mexico City | 19.4333 | -99.1333
New York-Newark | 40.8086 | -74.0204
Sao Paulo | -23.5478 | -46.6358
#python3.6以下
>>> fmt = '{:15} | {:9.4f} | {:9.4f}'
>>> for name,cc,pop,(latitude,longitude) in metro_areas:
... 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
2.3 具名元组
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'
>>> tokyo[3]
(35.689722, 139.691667)
#_fields属性是一个包含这个类所有字段名称的元组
>>> City._fields
('name', 'country', 'population', 'coordinates')
>>> LatLong = namedtuple('LatLong','lat long')
>>> delhi_data = ('Delhi NCR','IN',21.935,(28.613889,77.208889))
#用_make()通过接受一个可迭代对象来生成这个类的一个实例,作用和City(*delhi_data)一样
>>> delhi = City._make(delhi_data)
#_asdict()把具名元组以collections.OrderedDict的形式返回
>>> delhi._asdict()
OrderedDict([('name', 'Delhi NCR'), ('country', 'IN'), ('population', 21.935), ('coordinates', (28.613889, 77.208889))])
>>> for key,value in delhi._asdict().items():
... print(key + ':', value)
...
name: Delhi NCR
country: IN
population: 21.935
coordinates: (28.613889, 77.208889)
由上面的例子可以看出,元组是一种很强大的当作记录来用的数据类型。

本文深入探讨Python序列,重点讲解列表推导和生成器表达式的使用,包括可读性、与filter和map的比较,以及它们在内存效率上的优势。同时,介绍了元组的拆包、嵌套元组拆包和具名元组的应用,展示了元组作为记录的强大功能。
404

被折叠的 条评论
为什么被折叠?



