flyEn'blog

python cookbook学习笔记(一)

此文仅为学习python cookbook书籍时候记的笔记,记录过程权当加深记忆,无任何主观内容,若需参考可直接至此在线文档学习。

——数据结构和算法

解压序列赋值给多个变量

1
2
>>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
>>> name, shares, price, date = data

前提是变量数量必须和序列元素数量一样。

否则异常:

1
2
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: need more than 2 values to unpack

适用对象:任何可迭代对象(列表、元组、字符串、文件对象、迭代器、生成器
解压一部分,可用任意变量名去占位。

1
2
>>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
>>> _, shares, price, _ = data

星号表达式

可迭代对象的元素个数超出变量个数,抛出ValueError,解决方式:

1
2
3
def drop_first_last(grades):
first, *middle, last = grades
return avg(middle)

middle变量是一个列表类型。

可用于for循环时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
records = [
('foo', 1, 2),
('bar', 'hello'),
('foo', 3, 4),
]

def do_foo(x, y):
print('foo', x, y)

def do_bar(s):
print('bar', s)

for tag, *args in records:
if tag == 'foo':
do_foo(*args)
elif tag == 'bar':
do_bar(*args)

可用于字符串分割时:

1
2
>>> line = 'nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false'
>>> uname, *fields, homedir, sh = line.split(':')

如果想解压后就丢弃,可使用一个废弃名称,比如 _ 或者 ign 。

1
2
>>> record = ('ACME', 50, 123.45, (12, 18, 2012))
>>> name, *_, (*_, year) = record

保留最后N个元素:collections.deque

保留最后n个元素deque(maxlen=n)
使用 deque(maxlen=N) 构造函数会新建一个固定大小的队列。当新的元素加入并且这个队列已满的时候, 最老的元素会自动被移除掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from collections import deque


def search(lines, pattern, history=5):
previous_lines = deque(maxlen=history)
for li in lines:
if pattern in li:
yield li, previous_lines
previous_lines.append(li)

# Example use on a file
if __name__ == '__main__':
with open(r'../../cookbook/somefile.txt') as f:
for line, prevlines in search(f, 'python', 5):
for pline in prevlines:
print(pline, end='')
print(line, end='')
print('-' * 20)

添加和弹出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> q = deque()
>>> q.append(1)
>>> q.append(2)
>>> q.append(3)
>>> q
deque([1, 2, 3])
>>> q.appendleft(4)
>>> q
deque([4, 1, 2, 3])
>>> q.pop()
3
>>> q
deque([4, 1, 2])
>>> q.popleft()
4

查找最大或最小元素:heapq

heapq模块有两个函数:nlargest() 和 nsmallest()

1
2
3
4
import heapq
nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
print(heapq.nlargest(3, nums)) # Prints [42, 37, 23]
print(heapq.nsmallest(3, nums)) # Prints [-4, 1, 2]

字典中的键映射多个值:defaultdict

1
2
3
4
5
6
7
8
9
10
11
from collections import defaultdict

d = defaultdict(list)
d['a'].append(1)
d['a'].append(2)
d['b'].append(4)

d = defaultdict(set)
d['a'].add(1)
d['a'].add(2)
d['b'].add(4)
1
2
3
4
d = {
'a':[1,2]
'b':[4]
}

普通字典:

1
2
3
d = {} # A regular dictionary
d.setdefault('a', []).append(1)
d.setdefault('a', []).append(2)

字典排序: OrderedDict

1
2
3
4
5
d = OrderedDict()
d['foo'] = 1
d['bar'] = 2
d['spam'] = 3
d['grok'] = 4

需要注意:orderedDict的大小是一个普通字典的两倍,因为它内部维护着另一个链表。

字典运算

zip()

1
2
3
4
5
6
7
prices = {
'ACME': 45.23,
'AAPL': 612.78,
'IBM': 205.55,
'HPQ': 37.20,
'FB': 10.75
}

zip(prices.values(), prices.keys()),用min()、max()、sorted()函数对其操作。

1
2
3
4
5
6
7
8
min_price = min(zip(prices.values(), prices.keys()))
# min_price is (10.75, 'FB')
max_price = max(zip(prices.values(), prices.keys()))
# max_price is (612.78, 'AAPL')
prices_sorted = sorted(zip(prices.values(), prices.keys()))
# prices_sorted is [(10.75, 'FB'), (37.2, 'HPQ'),
# (45.23, 'ACME'), (205.55, 'IBM'),
# (612.78, 'AAPL')]

注意:zip()函数创建的是一个只能访问一次的迭代器。

1
2
3
prices_and_names = zip(prices.values(), prices.keys())
print(min(prices_and_names)) # OK
print(max(prices_and_names)) # ValueError: max() arg is an empty sequence

查找两字典的相同点

a = { ‘x’ : 1, ‘y’ : 2, ‘z’ : 3}
b = { ‘w’ : 10, ‘x’ : 11, ‘y’ : 2}

1
2
3
4
5
6
# 相同的key
a.keys() & b.keys() # { 'x', 'y' }
# 在a不在b的key
a.keys() - b.keys() # { 'z' }
# 相同的元素
a.items() & b.items() # { ('y', 2) }

去重复值:dedupe

1
2
3
4
5
6
def dedupe(items):
seen = set()
for item in items:
if item not in seen:
yield item
seen.add(item)
1
2
3
4
>>> a = [1, 5, 2, 1, 9, 1, 5, 10]
>>> list(dedupe(a))
[1, 5, 2, 9, 10]
>>>

前提:序列上的值都是hashable类型

1
2
3
4
5
6
>>> a = [ {'x':1, 'y':2}, {'x':1, 'y':3}, {'x':1, 'y':2}, {'x':2, 'y':4}]
>>> list(dedupe(a, key=lambda d: (d['x'],d['y'])))
[{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
>>> list(dedupe(a, key=lambda d: d['x']))
[{'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
>>>

简单的消除重复元素,也可简单构造一个集合

1
2
3
4
5
>>> a
[1, 5, 2, 1, 9, 1, 5, 10]
>>> set(a)
{1, 2, 10, 5, 9}
>>>

不能维护元素的顺序

命名切片: slice()

1
2
3
4
5
6
7
8
record = '....................100 .......513.25 ..........' 
# 从记录字符串中几个固定位置取出特定数据字段。
# 一:
cost = int(record[20:23]) * float(record[31:37])
# 二:
SHARES = slice(20, 23)
PRICE = slice(31, 37)
cost = int(record[SHARES]) * float(record[PRICE])

第二种方式避免了大量无法理解的硬编码下标。
内置的slice()函数创建了一个切片对象,可以被用在任何切片允许使用的地方。

序列中出现次数最多的元素:Counter.most_common()

1
2
3
4
5
6
7
words = [...]
from collections import Counter
word_counts = Counter(words)
# 出现频率最高的3个单词
top_three = word_counts.most_common(3)
print(top_three)
# Outputs [('eyes', 8), ('the', 5), ('look', 4)]

在底层实现上,一个 Counter 对象就是一个字典,将元素映射到它出现的次数上。

1
2
word_counts['eyes'] 
>>>8

Counter 实例可以很容易的跟数学运算操作相结合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> a = Counter(words)
>>> b = Counter(morewords)
>>> a
Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2,
"you're": 1, "don't": 1, 'under': 1, 'not': 1})
>>> b
Counter({'eyes': 1, 'looking': 1, 'are': 1, 'in': 1, 'not': 1, 'you': 1,
'my': 1, 'why': 1})
>>> # Combine counts
>>> c = a + b
>>> c
Counter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2,
'around': 2, "you're": 1, "don't": 1, 'in': 1, 'why': 1,
'looking': 1, 'are': 1, 'under': 1, 'you': 1})
>>> # Subtract counts

通过某个关键字排序一个字典列表 operator.itemgetter

1
2
rows_by_fname = sorted(rows, key=itemgetter('fname'))
rows_by_uid = sorted(rows, key=itemgetter('uid'))

排序不支持比较的对象

1
2
users = [User(23), User(3), User(99)]
print(sorted(users, key=lambda u: u.user_id))

另外一种方式是使用 operator.attrgetter()来代替lambda函数:

1
2
3
>>> from operator import attrgetter
>>> sorted(users, key=attrgetter('user_id'))
[User(3), User(23), User(99)]

attrgetter() 函数通常会运行的快点,并且还能同时允许多个字段进行比较。
也同样适用于min()和max()之类的函数。

通过某个字段将记录分组: itertools.groupby()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from operator import itemgetter
from itertools import groupby

rows = [
{'address': '5412 N CLARK', 'date': '07/01/2012'},
{'address': '5148 N CLARK', 'date': '07/04/2012'},
{'address': '5800 E 58TH', 'date': '07/02/2012'},
...
]
# Sort by the desired field first
rows.sort(key=itemgetter('date'))
# Iterate in groups
for date, items in groupby(rows, key=itemgetter('date')):
print(date)
for i in items:
print(' ', i)

首先需要按照指定的字段(这里就是 date )排序

结果:

1
2
3
4
5
6
07/01/2012
{'date': '07/01/2012', 'address': '5412 N CLARK'}
{'date': '07/01/2012', 'address': '4801 N BROADWAY'}
07/02/2012
{'date': '07/02/2012', 'address': '5800 E 58TH'}
...

如果将数据分组到一个大的数据结构中去,并允许随机访问,defaultdict()

1
2
3
4
5
6
7
8
9
10
11
from collections import defaultdict
rows_by_date = defaultdict(list)
for row in rows:
rows_by_date[row['date']].append(row)

>>> for r in rows_by_date['07/01/2012']:
... print(r)
...
{'date': '07/01/2012', 'address': '5412 N CLARK'}
{'date': '07/01/2012', 'address': '4801 N BROADWAY'}
>>>

如果对内存占用不是很关心, 这种方式会比先排序然后再通过 groupby() 函数迭代的方式运行得快一些。

过滤序列元素

一:使用列表推导。

1
2
3
>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]
>>> [n for n in mylist if n > 0]
[1, 4, 10, 2, 3]

潜在缺陷就是如果输入非常大的时候会产生一个非常大的结果集,占用大量内存。
二:如果对内存比较敏感,可使用生成器表达式迭代过滤的元素。

1
2
3
4
5
>>> pos = (n for n in mylist if n > 0)
>>> pos
<generator object <genexpr> at 0x1006a0eb0>
>>> for x in pos:
... print(x)

从字典里提取子集:

使用字典推导:

1
2
3
4
5
6
7
8
9
10
11
12
prices = {
'ACME': 45.23,
'AAPL': 612.78,
'IBM': 205.55,
'HPQ': 37.20,
'FB': 10.75
}
# Make a dictionary of all prices over 200
p1 = {key: value for key, value in prices.items() if value > 200}
# Make a dictionary of tech stocks
tech_names = {'AAPL', 'IBM', 'HPQ', 'MSFT'}
p2 = {key: value for key, value in prices.items() if key in tech_names}

映射名称到序列元素: collections.namedtuple()

通过下标访问列表或者元组中元素的代码,但这样有时候会使得你的代码难以阅读,可用此通过名称来访问元素。

1
2
3
4
5
6
>>> Subscriber = namedtuple('Subscriber', ['addr', 'joined'])
>>> sub = Subscriber('jonesy@example.com', '2012-10-19')
>>> sub.addr
'jonesy@example.com'
>>> sub.joined
'2012-10-19'

nametuple和元组类型是可交换的,支持所有的普通元组操作,比如索引和解压。

1
2
3
4
5
6
7
8
>>> len(sub)
2
>>> addr, joined = sub
>>> addr
'jonesy@example.com'
>>> joined
'2012-10-19'
>>>

命名元组的一个主要用途是将你的代码从下标操作中解脱出来。
命名元组另一个用途就是作为字典的替代,因为字典存储需要更多的内存空间。。 如果你需要构建一个非常大的包含字典的数据结构,那么使用命名元组会更加高效。 但是需要注意的是,不像字典那样,一个命名元组是不可更改的。

转换并同时计算数据

一个非常优雅的方式去结合数据计算与转换就是使用一个生成器表达式参数。

1
2
nums = [1, 2, 3, 4, 5]
s = sum(x * x for x in nums)

合并多个字典或映射:collections.ChainMap

现在有多个字典或者映射,你想将它们从逻辑上合并为一个单一的映射后执行某些操作。

1
2
a = {'x': 1, 'z': 3 }
b = {'y': 2, 'z': 4 }

在两个字典中执行查找操作(比如先从a中找,如果找不到再在b中找)

1
2
3
4
from collections import ChainMap
c = ChainMap(a,b)
print(c['x']) # Outputs 1 (from a)
print(c['y']) # Outputs 2 (from b)

一个 ChainMap 接受多个字典并将它们在逻辑上变为一个字典。 然后,这些字典并不是真的合并在一起了, ChainMap 类只是在内部创建了一个容纳这些字典的列表 并重新定义了一些常见的字典操作来遍历这个列表。大部分字典操作都是可以正常使用的。

如果出现重复键,那么第一次出现的映射值会被返回。对于字典的更新或删除操作总是影响的是列表中第一个字典。
作为 ChainMap的替代,你可能会考虑使用 update() 方法将两个字典合并。

1
2
3
4
5
6
>>> a = {'x': 1, 'z': 3 }
>>> b = {'y': 2, 'z': 4 }
>>> merged = dict(b)
>>> merged.update(a)
>>> merged['x']
1

这样也能行得通,但是它需要你创建一个完全不同的字典对象(或者是破坏现有字典结构)。 同时,如果原字典做了更新,这种改变不会反应到新的合并字典中去。

Fork me on GitHub