第二章:序列构成数组

2.1 内置序列类型概览

容器序列:存放的是它们所包含的任意类型的对象的引用
list、tuple、和collections.deque 这些序列可以存放不同的数据类型

扁平序列:存放的是值而不是引用
str 、 bytes 、 bytearray 、memoryview 和 array.array 这类序列只能容纳一种类型。

换句话说,扁平序列其实是一种连续的内存空间。由此可见扁平序列其实更加紧凑,但是它里面只能存放诸如字符串、字节和数值这种基础类型。

序列类型还能按照能否被修改分类:

可变序列: list、bytearray、array.array 、 collections.deque 和 memoryview

不可变序列: tuple 、str 、bytes

2.2列表推导式

列表推导是构建列表(list)的快捷方式,而生成器表达式则可以用来创建其他任意序列。
很多python程序员都把列表推导式(list comprehension) 简称为listcomps,生成式表达器(generator expression)则称为 genexps。

2.2.1 列表推导和可读性

symbols = '$¢£¥€¤' 
beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
beyond_ascii
1

如果用if else 写这段代码 需要很多的步骤 :

symbols = '$¢£¥€¤' 
codes = []
for symbols in symbols:
    if ord(symbols) > 127:
        codes.append(ord(symbols))
print(codes)

用什么方法并没有硬性的规定,舒服就好。

句法提醒:python会忽略代码里[]、{}、和()中的换行,因此如果你的代码里有很多行的列表、列表推导式、生成器表达式、字典这一类的,可以省略不太好看的续航符\。

列表推导可以帮助我们把一个序列或是其他可迭代类型中的元素过滤或是加工,然后再新 建一个列表。Python 内置的 filter 和 map 函数组合起来也能达到这一效果,但是可读性上 打了不小的折扣。

2.2.2 列表推导同filter 和 map的比较

symbols中的所有元素都先转化为ord,之后使用filter进行过滤 过滤条件为大于127.

symbols = '$¢£¥€¤' 
beyond_ascii = list(filter(lambda c : c>127 , map(ord , symbols)))
beyond_ascii
2

2.2.3 笛卡尔积

笛卡尔积:两个list之间的元素两两组合

colors = ['black' , 'white']
sizes = ['S' , 'M' ,'L']

tshirts = [(color , size ) for color in colors for size in sizes]
tshirts
3

for循环方式做笛卡尔积

colors = ['black' , 'white']
sizes = ['S' , 'M' ,'L']

for color in colors:
    for size in sizes:
        print((color , size))
4

2.2.4生成器表达式

虽然也可以用列表推导来初始化元组、数组或其他序列类型,但是生成器表达式是更好的 选择。这是因为生成器表达式背后遵守了迭代器协议,可以逐个地产出元素,而不是先建 立一个完整的列表,然后再把这个列表传递到某个构造函数里。前面那种方式显然能够节 省内存。
生成器表达式的语法跟列表推导差不多,只不过把方括号换成圆括号而已。

用生成器表达式初始化元组和数组

symbols = '$¢£¥€¤' 
tuple(ord(symbol) for symbol in symbols)

import array
array.array('I' , (ord(symbol) for symbol in symbols))
5

如果生成器表达式是一个函数调用过程中的唯一参数,那么不需要额外再用括号把它围起来。

array 的构造方法需要两个参数,因此括号是必需的。array 构造方法的第一个参数指 定了数组中数字的存储方式

生成器表达式逐个产出元素,从来不会一次性产出一个含有 6 个 T 恤样式的列表。

使用生成器表达笛卡尔积:用到生成器表达式之后, 内存里不会留下一个有 6 个组合的列表,因为生成器表达式会在每次 for 循环运行时才生 成一个组合

colors = ['black', 'white'] 
sizes = ['S', 'M', 'L']
for tshirt in ('%s %s' % (c, s) for c in colors for s in sizes):
     print(tshirt) 
6

2.3 元组不仅仅是不可变的列表

有些 Python 入门教程把元组称为“不可变列表”,然而这并没有完全概括元组的特点。除 了用作不可变的列表,它还可以用于没有字段名的记录。鉴于后者常常被忽略,我们先来 看看元组作为记录的功用

2.3.1 元组和记录

元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个 字段的位置。正是这个位置信息给数据赋予了意义。
如果只把元组理解为不可变的列表,那其他信息——它所含有的元素的总数和它们的位 置——似乎就变得可有可无。但是如果把元组当作一些字段的集合,那么数量和位置信息 就变得非常重要了。

元组的记录作用
元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个 字段的位置。正是这个位置信息给数据赋予了意义。
如果只把元组理解为不可变的列表,那其他信息——它所含有的元素的总数和它们的位 置——似乎就变得可有可无。但是如果把元组当作一些字段的集合,那么数量和位置信息 就变得非常重要了。

把元组用作记录

按照traveler_ids进行排序,输出 国家/id的形式

lax_coordinates = (33.9425 , -118.408056)

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)

7
for country , _ in traveler_ids :
    print(country)
8

如果直接对traveler_ids 进行拆包,那么会得到 (国家,id)的表达式,但是再对tuple进行拆解成国家,id这样的表达式,结果中只需要country这个字段,用_代替id字段。

2.3.2 元组拆包

最好辨认的元组拆包形式就是平行赋值,也就是说把一个可迭代对象里的元素,一并赋值 到由对应的变量组成的元组中。就像下面这段代码:

lax_coordinates = (33.9425, -118.408056) 
latitude, longitude = lax_coordinates
print(latitude)
print(longitude)
9

还可以用*运算符把一个可迭代对象拆开作为函数的参数:

divmod(20,8)
10
t = (20,8)
divmod(*t)
11

下面是另一个例子,这里元组拆包的用法则是让一个函数可以用元组的形式返回多个值, 然后调用函数的代码就能轻松地接受这些返回值。比如 os.path.split() 函数就会返回以 路径和最后一个文件名组成的元组 (path, last_part):

import os 
_, filename = os.path.split('/home/luciano/.ssh/idrsa.pub') 
filename
12

在进行拆包的时候,我们不总是对元组里所有的数据都感兴趣,_ 占位符能帮助处理这种 情况,上面这段代码也展示了它的用法

除此之外,在元组拆包中使用 * 也可以帮助我们把注意力集中在元组的部分元素上。

用来*处理剩下的元素
在python中 *arg来获取不确定数量的参数算是一种经典的写法了

a , b , *rest = range(5)

a , b , rest
13

*变量的输出结果会以一个list的形式存在

2.3.3 嵌套和拆包

接受表达式的元组可以是嵌套式的,例如 (a, b, (c, d))。只要这个接受元组的嵌套结构 符合表达式本身的嵌套结构,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)), ] 

print('{:15} | {:^9} | {:^9}'.format('', 'lat.', 'long.'))
fmt = '{:15} | {:9.4f} | {:9.4f}' 

for name, cc, pop, (latitude, longitude) in metro_areas: 
     if longitude <= 0:
             print(fmt.format(name, latitude, longitude))

每个元组内有 4 个元素,其中最后一个元素是一对坐标。
我们把输入元组的最后一个元素拆包到由变量构成的元组里,这样就获取了坐标。
if longitude <= 0: 这个条件判断把输出限制在西半球的城市
最后的输出结果:


14

2.3.4 具名元组

collections.namedtuple 是一个工厂函数,它可以用来构建一个带字段名的元组和一个有字段名的类——这个带名字的类对调试程序有很大的帮助

定义和使用具体元组

from collections import namedtuple
City = nameldtuple('City' , 'name country population coordinates')
tokyo = City('Tokyo' , 'JP' , 36.933 , (35.689722 , 139.691667))
tokyo
15
print(tokyo.population)
print(tokyo.coordinates)
print(tokyo[1])
16

2.4切片

在 Python 里,像列表(list)、元组(tuple)和字符串(str)这类序列类型都支持切片 操作,但是实际上切片操作比人们所想象的要强大很多。

2.4.1 为什么切片和区间会忽略最后一个元素(左闭右开)

在切片和区间操作里不包含区间范围的最后一个元素是Python 的风格,这个习惯符合 Python、C 和其他语言里以 0 作为起始下标的传统

2.4.2 对对象进行切片

2.4.3 多维切片和省略

2.4.4 给切片赋值

(平时常用,就不详细记录了)

2.5 对序列使用 + 和 *

2.6 序列的增量赋值

增量赋值运算符 += 和 *= 的表现取决于它们的第一个操作对象。简单起见,我们把讨论集 中在增量加法(+=)上,但是这些概念对 *= 和其他增量运算符来说都是一样的。
+= 背后的特殊方法是 iadd(用于“就地加法”)。但是如果一个类没有实现这个方法的 话,Python 会退一步调用 add

a + b

如果a 实现了iadd 方 法, 就 会 调 用 这 个 方 法。 同 时 对 可 变 序 列( 例 如list、 bytearray 和 array.array)来说,a 会就地改动,就像调用了 a.extend(b) 一样。但是如 果 a 没有实现 iadd 的话,a += b 这个表达式的效果就变得跟 a = a + b 一样了:首先 计算 a + b,得到一个新的对象,然后赋值给 a。也就是说,在这个表达式中,变量名会不 会被关联到新的对象,完全取决于这个类型有没有实现 iadd 这个方法。
总体来讲,可变序列一般都实现了 iadd 方法,因此 += 是就地加法。而不可变序列根 本就不支持这个操作,对这个方法的实现也就无从谈起。
上面所说的这些关于 += 的概念也适用于 *=,不同的是,后者相对应的是 imul。关于 iaddimul,第 13 章中会再次提到。

例子:

l = [1,2,3]
print(id(l))
l *= 2
print(l)
print(id(l))
17
t = (1,2,3)
print(id(t))
t *= 2 
print(id(t))
18

2.7list.sort方法和内置函数sorted

list.sort 方法会就地排序列表,也就是说不会把原列表复制一份。这也是这个方法的返 回值是 None 的原因,提醒你本方法不会新建一个列表。在这种情况下返回 None 其实是 Python 的一个惯例:如果一个函数或者方法对对象进行的是就地改动,那它就应该返回 None,好让调用者知道传入的参数发生了变动,而且并未产生新的对象。例如,random. shuffle 函数也遵守了这个惯例。

用返回 None 来表示就地改动这个惯例有个弊端,那就是调用者无法将其串 联起来。而返回一个新对象的方法(比如说 str 里的所有方法)则正好相 反,它们可以串联起来调用,从而形成连贯接口(fluent interface)。详情 参见维基百科中有关连贯接口的讨论(https://en.wikipedia.org/wiki/Fluent_ interface)。
与 list.sort 相反的是内置函数 sorted,它会新建一个列表作为返回值。这个方法可以接 受任何形式的可迭代对象作为参数,甚至包括不可变序列或生成器(见第 14 章)。而不管 sorted 接受的是怎样的参数,它最后都会返回一个列表。
不管是 list.sort 方法还是 sorted 函数,都有两个可选的关键字参数。
reverse
如果被设定为 True,被排序的序列里的元素会以降序输出(也就是说把最大值当作最 小值来排序)。这个参数的默认值是 False。
key
一个只有一个参数的函数,这个函数会被用在序列里的每一个元素上,所产生的结果 将是排序算法依赖的对比关键字。比如说,在对一些字符串排序时,可以用 key=str. lower 来实现忽略大小写的排序,或者是用 key=len 进行基于字符串长度的排序。这个参数的默认值是恒等函数(identity function),也就是默认用元素自己的值来排序。

可选参数 key 还可以在内置函数 min() 和 max() 中起作用。另外,还有些标准 库里的函数也接受这个参数,像 itertools.groupby() 和 heapq.nlargest() 等。

2.8 用bisect 来管理已排序的序列

bisect 模块包含两个主要函数,bisect 和 insort,两个函数都利用二分查找算法来在有序 序列中查找或插入元素。

bisect(haystack, needle) 在 haystack(干草垛)里搜索 needle(针)的位置,该位置满 足的条件是,把 needle 插入这个位置之后,haystack 还能保持升序。也就是在说这个函 数返回的位置前面的值,都小于或等于 needle 的值。其中 haystack 必须是一个有序的序 列。你可以先用 bisect(haystack, needle) 查找位置 index,再用 haystack.insert(index, needle) 来插入新值。但你也可用 insort 来一步到位,并且后者的速度更快一些。

在有序序列中用bisect查找某个元素的插入位置

import bisect
import os 

HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30]
NEEDLES = [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(NEEDLES):         
        position = bisect_fn(HAYSTACK, needle) 
        offset = position * '  |' 
        print(ROW_FMT.format(needle, position, offset)) 

if __name__ == '__main__': 
     if sys.argv[-1] == 'left': 
          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)

2.9 数组以及numpy array scipy 平时使用较多 快速浏览就不记录了

你可能感兴趣的:(第二章:序列构成数组)