第二章 - 序列构成的数组

2.1 内置序列类型概览

Python标准库用C实现了丰富的序列类型;

容器序列

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

扁平序列

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

可变序列

  list, bytearray, array.array, collections.deque, memoryivew.

不可变序列

  tuple, str, bytes.

 

2.2 列表推导和生成器表达式

filter和map合起来能做的事情,列表推导也能做,而且还不需要借助难以理解和阅读的lambda表达式。

def mul(x):
    return x * x

def compare(x):
    return True if x > 2 else False

aa = map(mul, [1,2,3,4,5])

bb = filter(compare, aa)
print(list(bb))
>>>
[4, 9, 16, 25]

cc = [x*x for x in [1,2,3,4,5] if x>2]
print(cc)
>>>
[9, 16, 25]

 

笛卡尔积(假设集合A={a, b},集合B={0, 1, 2},则两个集合的笛卡尔积为{(a, 0), (a, 1), (a, 2), (b, 0), (b, 1), (b, 2)}。)

colors = ["black", "white"]
sizes = ["S", "M", "L"]
t_shirts = [(color, size)
            for color in colors
            for size in sizes]

print(t_shirts)
>>>
[('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')]

t_shirts = [(color, size)  # 自动按照size排序
for size in sizes
for color in colors
]

print(t_shirts)
>>>
[('black', 'S'), ('white', 'S'), ('black', 'M'), ('white', 'M'), ('black', 'L'), ('white', 'L')]

 

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

3大功能:

  记录数据

  不可变列表

  拆包

2.3.1 元组和记录

元组其实是对数据的记录: 元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置。正是这个位置信息给数据赋予了意义。

traveler_ids = [("USA", "31195855"), ("BRA", "CE342567"), ("ESP", "XDA205856")]

for passport in sorted(traveler_ids):
    print("%s/%s" % passport)

for country, _ in traveler_ids:  # _ 占位符(一个位)
    print(country)

for country, *_ in traveler_ids:  # *_ 占位符(多个位)
    print(country)

t = (20, 8)
a, b = divmod(*t)  # 元组拆包
print(a, b)
>>>
2 4


def TT(**kwargs):
print(kwargs)
tt = {"a": 11, "b": 33}
TT(**tt)  # 字典拆包
>>>
{'a': 11, 'b': 33}

import os

_, filename = os.path.split("/home/cloud/.ssh/idrsa.pub")
print(filename)
>>>
idrsa.pub

a, b, *rest = range(10)
print(a, b, rest)
>>>
0 1 [2, 3, 4, 5, 6, 7, 8, 9]

aa, bb, *rests = range(2)
print(aa, bb, rests)
>>>
0 1 []

*head, aaa, bbb = range(10)
print(head, aaa, bbb)
>>>
[0, 1, 2, 3, 4, 5, 6, 7] 8 9

 

2.3.4 命名元组

collections.namedtuple 是一个工厂函数(工厂函数:看着像一个函数,实际上是一个类;当调用它是,实际上是生成了该类的一个实例,就像是工厂的流水线;int, dict, tuple, list 都是内置工厂函数), 它可以用来构建一个带字段名的元组和一个有名字的类。

用namedtuple构建的类的实例所消耗的内存跟元组是一个样的,因为字段名都被存放在对应的类里面。

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))

print(City._fields)     # 显示这个类所包含的字段名称
>>>
('name', 'country', 'population', 'coordinates')

print(tokyo._asdict())    # 以collections.OrderedDict形式返回
>>>
OrderedDict([('name', 'Tokyo'), ('country', 'JP'), ('population', 36.933), ('coordinates', (35.689722, 139.691667))])

 

2.3.5 不可变列表的元组

除了跟增减元素相关的方法之外,元组支持列表的其他所有方法。

 

2.4 切片

s = "bicycle"

print(s[::-1])  # 对字符串反转 
>>>
elcycib

print(s[::-2])
>>>
eccb

给切片赋值

l = list(range(10))
print(l)
>>>
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

l[2: 5] = [20, 30]  # 必须是可迭代对象
print(l)
>>>
[0, 1, 20, 30, 5, 6, 7, 8, 9]

 

2.5 对序列使用+和*

+和* 都遵循不修改原有的操作对象,而是构建一个全新的序列。

 

2.6 序列的增量赋值

+=背后的特殊方法是__iadd__方法。

>>> a += b

如果a实现了__iadd__方法,就会调用这个方法,同时对可变序列来说, a就会原地改动,就像调用了 a.extend(b) 一样。但是如果a没有实现__iadd__, a += b这个表达式的效果就变得跟 a = a + b 一样了: 首先会计算 a+b 得到一个新的对象,然后赋值给a。

可变序列一般都实现了__iadd__方法, 因此 +=是就地加法。而不可变序列根本就不支持这个操作,对这个方法的实现也就无从谈起。

注意: str是一个例外,因为Cpython对字符串 +=做了优化,因为字符串拼接太重要了。

*= 对应的是 __imul__方法。

 

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

list.sort方法会就地排序列表,也就是说不会创建一个副本,并且返回值为None。

与list.sort相反的是内置函数sorted, 它会创建一个新的列表作为返回值; 该方法可以接收任何形式的可迭代对象,包括不可变序列或生成器。

list.sort和sorted函数都有两个可选的关键字参数: reverse   key

  reverse: 如果被设定为True, 被排序的序列里的元素会以降序输出, 这个参数默认为Flase(也就是升序排列)

  key: 一个只有一个参数的函数,这个函数会被用在序列的每一个元素上,所产生的结果将是排序算法依赖的对比关键字。 key=str.lower来实现忽略大小写的排序,或者用key=len进行基于字符串长度的排序。

l = [28, 14, "28", 5, "9", "1", 0, 6]
l.sort(key=int)
print(l)
>>>
[0, '1', 5, 6, '9', 14, 28, '28']

l = [28, 14, "28", 5, "9", "1", 0, 6]
l.sort(key=str)
print(l)
>>>
[0, '1', 14, 28, '28', 5, 6, '9']

 

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

bisect模块包含两个主要函数: bisect(利用二分查找算法在有序列中查找元素), insort(利用二分查找算法在有序列中插入元素)

2.8.1 用bisect(haystack, needle)来查找元素的位置index

  可以先用bisect(haystack, needle)查找位置index, 然后再用haystack.insert(index, needle)来插入新值。但是你也可以用insort来一步到位,并且后者的速度更快一些。

import bisect

HAYSTACK = [1, 3, 4, 5, 6, 7, 8, 11, 22, 33, 44, 88]

a = bisect.bisect(HAYSTACK, 9)
print(a)
>>>
7

bisect函数其实是bisect_right函数的别名,后者还有一个姊妹函数叫 bisect_left.

示例: 根据一个分数,查找到他对应的成绩

import bisect

def grade(score, breakpoints=[60, 70, 80, 90], grades="FDCBA"):
    i = bisect.bisect(breakpoints, score)
    return grades[i]

scores = [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
print(scores)
>>>
['F', 'A', 'C', 'C', 'B', 'A', 'A']

 

 2.8.2 用bisect.insort插入新元素

排序很耗时,因此在得到一个有序序列之后,我们最好能够保持它的有序。bisect.insort 就是为了这个为存在的。

import bisect

HAYSTACK = [1, 3, 4, 5, 6, 7, 8, 11, 22, 33, 44, 88]
bisect.insort(HAYSTACK, 9)
print(HAYSTACK)
>>>
[1, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 88]

 

2.9 当列表不是首选时

如果我们需要一个只包含数字的列表, 那么array.array比list更高效。 数组支持所有跟可变序列有关的操作, 包括.pop、 .insert、.extend。

另外数组还提供从文件读取和存入文件的更快的方法 .frombytes .tofile

 

2.9.2 内存视图

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

memoryview + bytesarray 减少socket字符串拼接的消耗。

 

2.9.4 双向队列和其他形式的队列

利用.append和.pop方法我们可以把列表当作栈或者队列来用(.append, .pop(0)结合起来,模拟栈的“先进先出”),但是删除列表的一个元素之类的操作是很耗时的,因为这些操作会牵扯到移动列表里的所有元素。

collections.deque类(双向队列)是一个线程安全,可以快速从两端添加或者删除元素的数据类型。

import collections

dq = collections.deque(range(10), maxlen=10)
print(dq)
>>>
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)

dq.rotate(3)
print(dq)
>>>
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)

dq.rotate(-4)
print(dq)
>>>
deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)

dq.appendleft(-1)
print(dq)
>>>
deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)

dq.extendleft([11,22,33])
print(dq)
>>>
deque([33, 22, 11, -1, 1, 2, 3, 4, 5, 6], maxlen=10)

append和popleft都是原子操作, 也就是说deque可以在多线程程序中安全地当作先进先出的栈使用,而使用者不用担心资源锁的问题。

posted @ 2017-07-18 23:17  Vincen_shen  阅读(164)  评论(0)    收藏  举报