第2章 序列构成的数组

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)的快捷方式,而生成器表达式则可以创建其他任何类型的序列。这两个工具都非常强大。

使用列表推导计算笛卡尔积
>>> 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')]

使用生成器表达式计算笛卡尔积
>>> colors = ['black','white']
>>> sizes = ['S','M','L']
>>> for tshirts in ('%s %s' % (c,s) for c in colors for s in sizes):
    print(tshirts)


black S
black M
black L
white S
white M
white L

2.3 元组不仅仅是不可变的列表
元组和记录
     元组中的每个元素都存放了记录中的一个字段的数据和这个字段的位置,而这个位置信息可以赋予特殊意义。
元组拆包
     元组拆包可以应用到任何可迭代对象上,唯一的硬性要求是被迭代对象和元组的元素数量是一致,除非我们用*来表述多余的元素。

>>> lax_coordinates = (33.9425, -118.408056)
>>> latitude, longitude = lax_coordinates
>>> latitude
33.9425
>>> longitude
-118.408056

嵌套元组拆包
#用牵头元组来获取经纬度
>>> metro_areas = [
    ('Tokyo','JP',36.933,(35.6598,139.69122)),
    ('Delhi NCP','IN',21.935,(28.6138,77.203154)),
    ('Mexico City','MX',20.142,(19.4333,-99.1333)),
]
>>> print('{:15} | {:^9} | {:^9}'.format('','lat.','long.'))
                |   lat.    |   long. 
>>> fmt = '{:15} | {:^9.4f} | {:9.4f}'
>>> for name, cc, pop, (latitude, longtitude) in metro_areas:
    if longtitude <= 0:
        print(fmt.format(name, latitude, longtitude))


Mexico City     |  19.4333  |  -99.1333
>>> 

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

#使用具名元组来记录一个城市的信息
>>> from collections import namedtuple
>>> City = namedtuple('City','name country population coordinates')
>>> tokyo = City('Tokyo','JP',36.933,(35.6889,139.4996))
>>> tokyo
City(name='Tokyo', country='JP', population=36.933, coordinates=(35.6889, 139.4996))
>>> tokyo.population
36.933
>>> tokyo.coordinates
(35.6889, 139.4996)
>>> tokyo[1]
'JP'

元组和列表的区别
除了跟增加元素相关的方法和__reversed__之外,元组支持列表其他的方法。

2.4 切片
列表、元组和字符串都支持切片
#以下是对对象切片和给切片赋值
>>> s = 'bicycle'
>>> s[::3]
'bye'
>>> s[::-1]
'elcycib'
>>> 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]

2.5 对序列使用+和*
通常+号两侧由相同类型的数据构成,在拼接过程中,两个序列 不会被修改,会新建一个序列来存储结果。*号也是一样。

2.6 序列的增量赋值
增量赋值运算符+=底层调用的方法取决于第一个操作对象:
  • 可变序列
               调用__iadd__方法,会改变对象,相当于调用了a.extend(b)一样。
  • 不可变序列
              调用__add__方法,不会改变对象,会新建一个对象存储结果。
其他增量赋值运算符都和+=相同。
#例子
>>> l = [1,2,3]
>>> id(l)
47343176
>>> l *= 2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l)
47343176
>>> t = (1,2,3)
>>> id(t)
47313304
>>> t *= 2
>>> id(t)
46894024

一个关于+=的谜题
#当在元组进行不同类型的自增会报错并且修改结果
>>> t = (1,2,[30,40])
>>> t[2] += [50,60]
Traceback (most recent call last):
  File "", line 1, in 
    t[2] += [50,60]
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])

下面来看看表达式s[a] += b生成的字节码,可能这个现象背后的原因会变得清晰。
>>> dis.dis('s[a] += b')
  1           0 LOAD_NAME                0 (s)
              2 LOAD_NAME                1 (a)
              4 DUP_TOP_TWO
              6 BINARY_SUBSCR                    (1)
              8 LOAD_NAME                2 (b)
             10 INPLACE_ADD                      (2)
             12 ROT_THREE
             14 STORE_SUBSCR                     (3)
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE
(1)将s[a]的值存入TOS(Top Of Stack,栈的顶端)。
(2)计算TOS += b。这一步能够完成,是因为TOS指向的是一个可变对象。
(3)s[a] = TOS赋值。这一步失败,是因为s是不可变的元组
至此我得到了3个教训
  • 不要把可变对象放在元组里面。
  • 增量赋值不是一个原子操作。
  • 查看Python的字节码并不难。

2.7 list.sort方法和内置函数sorted
list.sort
  • 就地排序列表,不会复制原列表,返回None。
内置函数sorted
  • 创建一个列表作为返回值。

两者都有两个可选关键参数:reverse(默认值为false,若为true则降序)和key(一个函数参数,例如按字符串长度排序的len、忽略大小写的lower来排序)
#例子
>>> fruits = ['grape','raspberry','apple','banana']
>>> sorted(fruits)
['apple', 'banana', 'grape', 'raspberry']
>>> fruits
['grape', 'raspberry', 'apple', 'banana']
>>> sorted(fruits, reverse=True)
['raspberry', 'grape', 'banana', 'apple']
>>> sorted(fruits, key=len)
['grape', 'apple', 'banana', 'raspberry']
>>> sorted(fruits, key=len, reverse=True)
['raspberry', 'banana', 'grape', 'apple']
>>> fruits
['grape', 'raspberry', 'apple', 'banana']
>>> fruits.sort()
>>> fruits
['apple', 'banana', 'grape', 'raspberry']

2.8 用bisect来管理已排序的序列
bisect模块包含两大主要函数,bisect和insort来进行二分查找算法来在有序序列中查找或插入元素。
#在有序序列中用bisect查找某个元素的插入位置
import bisect
import sys

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)             (1)
        offset = position * ' |'                           (2)
        print(ROW_FMT.format(needle, position, offset))    (3)

if __name__ == '__main__':
    if sys.argv[-1] == 'left':                             (4)
        bisect_fn = bisect.bisect_left
    else :
        bisect_fn = bisect.bisect

    print('DEMO:', bisect_fn.__name__)                     (5)
    print('haystack ->', ''.join('%2d' % n for n in HAYSTACK))
    demo(bisect_fn)
#结果
haystack ->  1 4 5 6 8121520212323262930
31 @ 14     | | | | | | | | | | | | | |31
30 @ 14     | | | | | | | | | | | | | |30
29 @ 13     | | | | | | | | | | | | |29
23 @ 11     | | | | | | | | | | |23
22 @  9     | | | | | | | | |22
10 @  5     | | | | |10
 8 @  5     | | | | |8
 5 @  3     | | |5
 2 @  1     |2
 1 @  1     |1
 0 @  0    0

(1)用bisect函数计算元素出现的位置。
(2)利用该位置算出几个分隔符。
(3)把元素和其应该出现的位置打印出来。
(4)根据命令上最后一个参数来选用bisect函数。
(5)把选定的函数在抬头打印出来。

用insort插入新元素可以保持有序序列的升序
import bisect
import random

SIZE = 7

random.seed(1729)

my_list = []
for i in range(SIZE):
    new_item = random.randrange(SIZE*2)
    bisect.insort(my_list, new_item)
    print('%2d ->' % new_item, my_list)
#结果
10 -> [10]
 0 -> [0, 10]
 6 -> [0, 6, 10]
 8 -> [0, 6, 8, 10]
 7 -> [0, 6, 7, 8, 10]
 2 -> [0, 2, 6, 7, 8, 10]
10 -> [0, 2, 6, 7, 8, 10, 10]

2.9 当列表不是首选时
如果只存储数字的列表的话,array.array比list更高效,因为数组在背后存的并不是数字的对象,而是数字的机器翻译,也就是字节表述。
如果需要频繁对序列做先进先出的操作,deque(双端队列)的速度更快。
如果是包含操作,则用set(集合)会更合适。

内存视图
memoryview是一个内置类,它能让用户在不复制内容的情况下操作一个数组的不同切片。

NumPy和SciPy
  • NumPy实现了多维同质数组和矩阵的操作。
  • SciPy提供很多科学计算有关的算法,专为线性代数、数值积分和统计学而设计。

双向队列
删除列表的第一个元素之类的操作是很耗时,因为这些操作会牵扯到移动列表里的所有元素。而collections.deque类(双向队列)是一个线程安全、可以快速从两端添加或删除元素的数据类型。

2.10 本章小结
     要想写出准确、高效和地道的Python代码,对标准库里的序列类型的掌握是不可或缺的。

     Python序列类型最常见的分类就是可变和不可变序列。但还有一种分类方式很有用,把它们分为扁平序列和容器序列:
  • 扁平序列的体积更小、速度更快而且使用起来更简单,但是它只保存一些原子性的数据,比如数字、字符和字节。
  • 容器序列则比较灵活,但是当容器序列遇到可变对象时,用户就需要格外小心了。

     列表推导和生成器表达式则提供灵活构建和初始化序列的方式,这两个工具都非常强大。

你可能感兴趣的:(流畅的Python)