Python Cookbook 学习笔记

1.数据结构和算法

搜索、排序、排列以及筛选等这一类常见的问题;常见的数据结构和同数据有关的算法。在collections模块中也包含了针对各种数据结构的解决方案。

1.1 将序列分解为单独的变量

# 变量赋值:要求是变量的总数和结构要与序列相吻合
name, shares, price, date = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
name, shares, price, (year, mon, day) = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
# 适用场景举例:丢弃不用的值
 _, shares, price, _ = [ 'ACME', 50, 91.1, (2012, 12, 21) ]

1.2 从任意长度的可迭代对象中分解元素

# 巧用*
# 适用场景:只对中间剩下的成绩做平均分统计
first, *middle, last = grades
grede_avg = avg(middle)
# 适用场景:一个人多个电话号码
for name, *phones in persons:
	pass
# 适用场景举例:丢弃不用的值
name, *ign, (*ign, year) = ('ACME', 50, 123.45, (12, 18, 2012))
# 不适用但可行场景:递归。递归真的不算是Python的强项,这是因为其内在的递归限制所致。
def sum(items):
	head, *tail = items
	return head + sum(tail) if tail else head
sum(items)

1.3 保存最后N个元素

从队列两端添加或弹出元素的复杂度都是O(1)。 这和列表不同,当从列表的头部插入或移除元素时,列表的复杂度为O(N)。
deque(maxlen=N) 创建了一个固定长度的队列。当有新记录加入而队列已满时会自动移除最老的那条记录。
如果不指定队列的大小,也就得到了一个无界限的队列,可以在两端执行添加和弹出操作。

from collections import deque
# 有限队列
q = deque(maxlen=3)
q.append(1)
q.append(2)
q.append(3)
q.append(4)		# deque([2, 3, 4], maxlen=3)
# 无限队列
q = deque()
q.append(1)
q.append(2)		# deque([1, 2])
q.appendleft(4)	# deque([4, 1, 2])
print(q.pop())	# 2
print(q)		# deque([4, 1])
print(q.popleft())	# 4

1.4 找到最大或最小的N个元素

N=1时,min()和max()更快;
N>1且相对较小时,函数nlargest()和nsmallest()才是最适用的(以堆的顺序排列的列表);
N和集合本身的大小差不多大,通常更快的方法是先对集合排序,然后做切片操作(例如,使用sorted(items)[:N]或者sorted(items)[-N:])。

heapq模块实现了一个小根堆。

  • heapify(x) : 将列表x转换为堆(小根堆)。
  • heappush(heap,item): 将item压入堆中。(heap使存放堆的数组)
  • heappop(heap): 从堆中弹出最小的项,并将其值返回。

heapq.heappush(pri_que, (freq, key)) 以 freq 的升序排列。最小的 freq 在堆顶,意味着该元素出现的频率最低。如果 freq 相同,则会比较第二个元素(key)。

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]
# 增加参数key
portfolio = [
{'name': 'IBM', 'shares': 100, 'price': 91.1},
{'name': 'AAPL', 'shares': 50, 'price': 543.22},
{'name': 'FB', 'shares': 200, 'price': 21.09},
{'name': 'HPQ', 'shares': 35, 'price': 31.75},
{'name': 'YHOO', 'shares': 45, 'price': 16.35},
{'name': 'ACME', 'shares': 75, 'price': 115.65}
]
cheap = heapq.nsmallest(3, portfolio, key=lambda s:s['price'])
''' cheap:
[{'name': 'YHOO', 'shares': 45, 'price': 16.35}, 
{'name': 'FB', 'shares': 200, 'price': 21.09}, 
{'name': 'HPQ', 'shares': 35, 'price': 31.75}]
'''
expensive = heapq.nlargest(3, portfolio, key=lambda s:s['price'])
''' expensive:
[{'name': 'AAPL', 'shares': 50, 'price': 543.22}, 
{'name': 'ACME', 'shares': 75, 'price': 115.65}, 
{'name': 'IBM', 'shares': 100, 'price': 91.1}]
'''
# 以堆的顺序排列的列表
nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
heap = list(nums)
heapq.heapify(heap)
print(heap)	# [-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8]
heapq.heappop(heap)	# -4
print(heap)	# [1, 2, 2, 23, 7, 8, 18, 23, 42, 37]
heapq.heappop(heap)	# 1
print(heap)	# [2, 2, 8, 23, 7, 37, 18, 23, 42]

1.6 在字典中将键映射到多个值上(defaultdict类)

from collections import defaultdict

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

d = defaultdict(set)
d['a'].add(1)

# 不用考虑对第一个值做初始化操作
d = defaultdict(list)
for key, value in pairs:
	d[key].append(value)
# 普通字典对比
d = {}
for key, value in pairs:
	if key not in d:
		d[key] = []
	d[key].append(value)

defaultdict()会自动创建字典表项以待稍后的访问(即使这些表项当前在字典中还没有找到)。如果不想要这个功能,可
以在普通的字典上调用setdefault()方法来取代。但setdefault()方法不方便,每次调用它时都会创建一个初始值的新实例了(例子中的空列表[])。

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

1.7 让字典保持有序(OrderedDict类)

严格按照元素初始添加的顺序进行,以便稍后对其做序列化或编码成另一种格式(如JSON编码)。
OrderedDict内部维护了一个双向链表,它会根据元素加入的顺序来排列键的位置。第一个新加入的元素被放置在链表的末尾。接下来对已存在的键做重新赋值不会改变键的顺序
OrderedDict的大小是普通字典的2倍多,这是由于它额外创建的链表所致。

from collections import OrderedDict
import json
d = OrderedDict()
d['foo'] = 1
d['bar'] = 2
json.dumps(d)	# '{"foo": 1, "bar": 2}'

1.8 与字典有关的计算问题(利用zip()实现值-键对)

直接处理字典,默认处理的是 ,而非值。

prices = {
	'ACME': 45.23,
	'AAPL': 612.78,
	'IBM': 205.55,
	'HPQ': 37.20,
	'FB': 10.75
}
print(min(prices))			# 'AAPL'
print(min(prices.keys()))	# 'AAPL'
print(min(prices.values()))	# 10.75

利用lambda获取键值关系

min(prices, key=lambda k: prices[k]) 			# 'FB'
prices[min(prices, key=lambda k: prices[k])]	# 10.75

利用zip()实现值-键对,获取键值关系

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()创建了一个迭代器,它的内容只能被消费一次。

prices_and_names = zip(prices.values(), prices.keys())
print(min(prices_and_names)) # (10.75, 'FB')
print(max(prices_and_names)) # ValueError: max() arg is an empty sequence

zip()元组上执行比较操作时,元素会依次比较。

prices = {'AAA': 45.23, 'ZZZ': 45.23}
print(min(zip(prices.values(), prices.keys())))  # (45.23, 'AAA')
print(max(zip(prices.values(), prices.keys())))  # (45.23, 'ZZZ')

1.9 在两个字典中寻找相同点(字典键的集合特性)

字典的键支持常见的集合操作,比如求并集、交集和差集。字典的 keys()方法items()方法 会返回keys-view对象,其中暴露了所有的键,且有唯一映射关系,故能直接使用keys-view对象而不必先将它们转化为集合。但values()方法并不支持集合操作

a = {
    'x': 1,
    'y': 2,
    'z': 3
}
b = {
    'w': 10,
    'x': 11,
    'y': 2
}
# 交集
print(a.keys() & b.keys())  # {'y', 'x'}
# 差集
print(a.keys() - b.keys())  # {'z'}
c = {key: a[key] for key in a.keys() - {'z', 'w'}}  # {'x': 1, 'y': 2}

1.10 从序列中移除重复项且保持元素间顺序不变

如果一个对象是可哈希的,那么在它的生存期内必须是不可变的,它需要有一个__hash__()方法。

可哈希:

  • 不可变基本数据类型:整数(int)、浮点数(float)、字符串(string)、元组(tuple)、布尔值(bool)
  • 不可变集合类型:冻结集合(frozenset)
    frozenset() -> empty frozenset object
    frozenset(iterable) -> frozenset object
    Build an immutable unordered collection of unique elements.(构建一个由唯一元素组成的 不可变 无序 集合。)
  • 自定义不可变对象:自定义类的实例是不可变且实现了__hash__()方法和__eq__()方法,那么这个类的是也是可哈希的。

不可哈希:

  • 可变基本数据类型:列表(list)、集合(set)、字典(dict)
a = [1, 5, 2, 1, 9, 1, 5, 10]
b = [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]

# 无序去重->集合
set(a)

# 保持元素间顺序不变的去重
# 可哈希
def dedupe_hashable(items):
    seen = set()
    for item in items:
        if item not in seen:
            yield item
            seen.add(item)      
print(dedupe(a))  # <generator object dedupe at 0x000001D5B71A2ED0>
print(list(dedupe(a)))  # [1, 5, 2, 9, 10]

# 兼容不可哈希
def dedupe_unhashable(items, key=None):
    seen = set()
    for item in items:
        val = item if key is None else key(item)
        if val not in seen:
            yield item
            seen.add(val)
print(list(dedupe_unhashable(b, key=lambda d: (d['x'], d['y']))))
# [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]

拓展:如文件行内容去重

with open(somefile,'r') as f:
	for line in dedupe(f):
		pass

1.11 对切片命名(slice)

用法:slice(stop) 或 slice(start, stop[, step])

items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
cut = slice(2, 9, 3)	# items[cut] == items[2:9:3]
print(cut.start)  	# 2
print(cut.stop)  	# 9
print(cut.step)  	# 3
print(items[cut]) 	# [2, 5, 8]
del items[cut]
print(items)  		# [0, 1, 3, 4, 6, 7, 9, 10]

indices(size)方法将切片映射到特定大小的序列上,返回一个(start, stop,step)元组,所有的值都已经恰当地限制在边界以内(当做索引操作时可避免出现IndexError异常)。下列结果存疑

s = '1234567890'
cut = slice(2, 8, 3)
print(cut.indices(len(s)))  # (2, 8, 3)
cut = slice(2, 8, -3)
print(cut.indices(len(s)))  # (2, 8, -3)
cut = slice(2, 18, 3)
print(cut.indices(len(s)))  # (2, 10, 3)
cut = slice(2, 18, -3)
print(cut.indices(len(s)))  # (2, 9, -3)
cut = slice(12, 8, 3)
print(cut.indices(len(s)))  # (10, 8, 3)
cut = slice(12, 8, -3)
print(cut.indices(len(s)))  # (9, 8, -3)
cut = slice(2, 8, 33)
print(cut.indices(len(s)))  # (2, 8, 33)
cut = slice(2, 8, -33)
print(cut.indices(len(s)))  # (2, 8, -33)
cut = slice(2, -8, 3)
print(cut.indices(len(s)))  # (2, 2, 3)
cut = slice(-2, 8, 3)
print(cut.indices(len(s)))  # (8, 8, 3)
cut = slice(-2, -8, 3)
print(cut.indices(len(s)))  # (8, 2, 3)
cut = slice(-2, -8, -3)
print(cut.indices(len(s)))  # (8, 2, -3)
# 实际使用时要用*将切片元组分解
for i in range(*cut.indices(len(s))):
    print(s[i])

1.12 找出序列中出现次数最多的元素(Counter类)

from collections import Counter

words = ['a', 'b', 'c', 'd', 'd', 'b', 'b', 'e', 'c', 'f', 'a']
morewords = ['c', 'c']

word_counts = Counter(words)
print(word_counts)  # Counter({'b': 3, 'a': 2, 'c': 2, 'd': 2, 'e': 1, 'f': 1})
print(word_counts.most_common(5))  # [('b', 3), ('a', 2), ('c', 2), ('d', 2), ('e', 1)]
print(word_counts.most_common(3))  # [('b', 3), ('a', 2), ('c', 2)]

# 在底层实现中,Counter是一个无序字典,键值对就是 元素-计数。计数值可以是任意的Interger(包括0和负数)。
print(word_counts['a'])  # 2
print(word_counts['z'])  # 0	# 查询不存在的键也不会报错

# 键值可直接计算
word_counts['a'] = word_counts['a'] + 5
print(word_counts['a'])  # 7
print(word_counts.most_common(3))  # [('a', 7), ('b', 3), ('c', 2)]

# 字典的update()会替换键值,而Counter的update()会累加
word_counts.update(morewords)
print(word_counts.most_common(3))  # [('a', 7), ('c', 4), ('b', 3)]

# Counter的数学计算
morewords_count = Counter(morewords)
print(word_counts)  # Counter({'a': 7, 'c': 4, 'b': 3, 'd': 2, 'e': 1, 'f': 1})
print(morewords_count)  # Counter({'c': 2})
print(word_counts + morewords_count)  # Counter({'a': 7, 'c': 6, 'b': 3, 'd': 2, 'e': 1, 'f': 1})
print(word_counts - morewords_count)  # Counter({'a': 7, 'b': 3, 'c': 2, 'd': 2, 'e': 1, 'f': 1})

# 重新建一个Counter实例,计数恢复
word_counts = Counter(words)
print(word_counts)  # Counter({'b': 3, 'a': 2, 'c': 2, 'd': 2, 'e': 1, 'f': 1})

1.13 通过公共键对字典列表排序(itemgetter)

性能上,itemgetter()通常会比lambda表达式运行得更快一些

rows = [
    {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
    {'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
    {'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
    {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
]

# 利用lambda
rows_by_fname = sorted(rows, key=lambda x: x['fname'])
rows_by_uid = sorted(rows, key=lambda x: x['uid'])
rows_by_lfname = sorted(rows, key=lambda x: (x['lname'], x['fname'],))
print(rows_by_fname)
print(rows_by_uid)
print(rows_by_lfname)
'''
[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}]
[{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}]
[{'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}]
'''

# 利用itemgetter,性能上通常会运行得更快一些
from operator import itemgetter
rows_by_fname = sorted(rows, key=itemgetter('fname'))
rows_by_uid = sorted(rows, key=itemgetter('uid'))
rows_by_lfname = sorted(rows, key=itemgetter('lname', 'fname'))
print(rows_by_fname)
print(rows_by_uid)
print(rows_by_lfname)
'''
[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}]
[{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}]
[{'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}]
'''

# 还使用与参数带查询标记key的,如max/min
print(min(rows, key=itemgetter('uid')))  # {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}
print(max(rows, key=itemgetter('uid')))  # {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值