1.2 解压可迭代对象赋值给多个变量
扩展的解压迭代语法
*号可以表示同一类型的集合
# 去掉首位元素
def drop_first_last(grades):
first, *middle, last = grades
return middle
list1 = [5,6,7,8,9,10]
print(drop_first_last(list1))
console:
[6, 7, 8, 9]
假设你现在有一些用户的记录列表,每条记录包含一个名字、邮件,接着就是不确定数量的电话号码。 你可以像下面这样分解这些记录:
record = ('Song', 'xxxxx@163.com', '111-111-111', '222-222-222', '333-333-333')
name , email ,*phone_numbers = record
# 此时只要用到phone_numbers都是列表类型
print(phone_numbers)
console:
['111-111-111', '222-222-222', '333-333-333']
# 用*来遍历可变长元组的序列时是很有用的。下面是一个带有标签的元组序列
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)
# 此处*args是多个参数,print中多个参数之间间隔默认为空格
for tag, *args in records:
if tag == 'foo':
do_foo(*args)
elif tag == 'bar':
do_bar(*args)
# 注意区别
for items in records:
print(items)
console:
foo 1 2
bar hello
foo 3 4
('foo', 1, 2)
('bar', 'hello')
('foo', 3, 4)
application in string:
>>> line = 'nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false'
>>> uname, *fields, homedir, sh = line.split(':')
>>> uname
'nobody'
>>> homedir
'/var/empty'
>>> sh
'/usr/bin/false'
>>>
解压一些元素后丢弃它们,你不能简单就使用 * , 但是你可以使用一个普通的废弃名称,比如 _
或者 ign
(*ign
即可使用)
>>> record = ('ACME', 50, 123.45, (12, 18, 2012))
>>> name, *_, (*_, year) = record
>>> name
'ACME'
>>> year
2012
>>>
下例可以提供一种递归的算法
items = [1, 10, 7, 4, 5, 9]
def sum(items):
head, *tail = items
return head + sum(tail) if tail else head
sum(items)
?1.3 保留最后N个元素
from collections import deque
def search(lines, pattern, history=5):
previous_lines = deque(maxlen=history)
for line in lines:
if pattern in line:
yield line, previous_lines
previous_lines.append(line)
# 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)
使用deque(maxlen=N)
构造函数会新建一个固定大小的队列。当新的元素加入并且这个队列已满的时候, 最老的元素会自动被移除掉
q = deque(maxlen=3)
q.append(1)
q.append(2)
q.append(3)
print(q)
q.append(4)
print(q)
q.append(5)
print(q)
console:
deque([1, 2, 3], maxlen=3)
deque([2, 3, 4], maxlen=3)
deque([3, 4, 5], maxlen=3)
1.4 查找最大或最小的 N 个元素
heapq
模块有两个函数:nlargest()
和 nsmallest()
可以完美解决这个问题。
import heapq
nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
print(heapq.nlargest(3, nums))
print(heapq.nsmallest(3, nums))
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(1, portfolio, lambda x:x['price'])
expensive = heapq.nlargest(1, portfolio, lambda x:x['price'])
print(cheap)
print(expensive)
console:
[42, 37, 23]
[-4, 1, 2]
[{'name': 'YHOO', 'shares': 45, 'price': 16.35}]
[{'name': 'AAPL', 'shares': 50, 'price': 543.22}]
堆数据结构最重要的特征是 heap[0] 永远是最小的元素。并且剩余的元素可以很容易的通过调用 heapq.heappop()
方法得到, 该方法会先将第一个元素弹出来,然后用下一个最小的元素来取代被弹出元素(这种操作时间复杂度仅仅是 O(log N),N 是堆大小)。 比如,如果想要查找最小的 3 个元素,你可以这样做:
当要查找的元素个数相对比较小的时候,函数 nlargest()
和 nsmallest()
是很合适的。 如果你仅仅想查找唯一的最小或最大(N=1)的元素的话,那么使用min()
和 max()
函数会更快些。 类似的,如果 N 的大小和集合大小接近的时候,通常先排序这个集合然后再使用切片操作会更快点 (sorted(items)[:N]
或者是 sorted(items)[-N:]
)。 需要在正确场合使用函数 nlargest()
和 nsmallest()
才能发挥它们的优势 (如果 N 快接近集合大小了,那么使用排序操作会更好些
import heapq
heap = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
print(min(heap))
print(max(heap))
print(sorted(heap)[:9])
heapq.heapify(heap)
print(heapq.heappop(heap))
print(heapq.heappop(heap))
print(heapq.heappop(heap))
console:
-4
42
[-4, 1, 2, 2, 7, 8, 18, 23, 23]
-4
1
2
1.5 实现一个优先级队列
函数heapq.heappush()
和 heapq.heappop()
分别在队列 _queue
上插入和删除第一个元素, 并且队列 _queue
保证第一个元素拥有最高优先级( 1.4 节已经讨论过这个问题)。 heappop()
函数总是返回”最小的”的元素,这就是保证队列pop操作返回正确元素的关键
引入index的作用是为了让优先级相同的情况下队列的比较不出错
import heapq
class PriorityQueue:
def __init__(self):
self._queue = []
self._index = 0
# 将优先级为负可以将其做优先级从高到低的处理,堆排序会将队列从低到高
def push(self, item, priority):
heapq.heappush(self._queue, (-priority, self._index, item))
self._index += 1
# 此处[-1]是为了取到队列中的最后一个元素即item,除去index和优先级
def pop(self):
return heapq.heappop(self._queue)[-1]
class Item:
def __init__(self, name):
self.name = name
def __repr__(self):
return 'Item({!r})'.format(self.name)
q = PriorityQueue()
q.push(Item('foo'), 1)
q.push(Item('bar'), 5)
q.push(Item('spam'), 4)
q.push(Item('grok'), 1)
print(q.__dict__)
print(q.pop())
print(q.pop())
print(q.pop())
print(q.pop())
console:
{'_queue': [(-5, 1, Item('bar')), (-1, 0, Item('foo')), (-4, 2, Item('spam')), (-1, 3, Item('grok'))], '_index': 4}
Item('bar')
Item('spam')
Item('foo')
Item('grok')
1.6字典中的键映射多个值
如果你想保持元素的插入顺序就应该使用列表, 如果想去掉重复元素就使用集合(并且不关心元素的顺序问题)。
d = {
'a' : [1, 2, 3],
'b' : [4, 5]
}
e = {
'a' : {1, 2, 3},
'b' : {4, 5}
}
你可以很方便的使用 collections
模块中的 defaultdict
来构造这样的字典。 defaultdict
的一个特征是它会自动初始化每个 key 刚开始对应的值,所以你只需要关注添加元素操作了
from collections import defaultdict
d = defaultdict(list)
d['a'].append(1)
d['a'].append(2)
d['b'].append(4)
print(d)
console:
defaultdict(<class 'list'>, {'a': [1, 2], 'b': [4]})
需要注意的是,defaultdict
会自动为将要访问的键(就算目前字典中并不存在这样的键)创建映射实体。 如果你并不需要这样的特性,你可以在一个普通的字典上使用 setdefault()
方法来代替。比如:
d = {} # A regular dictionary
d.setdefault('a', []).append(1)
d.setdefault('a', []).append(2)
d.setdefault('b', []).append(4)
1.7字典排序
from collections import OrderedDict
import json
d = OrderedDict()
d['foo'] = 1
d['bar'] = 'y'
d['spam'] = 3
d['grok'] = 4
print(d)
for key in d:
print(key, d[key])
# 将字典转化为json字符串
print(json.dumps(d))
console:
OrderedDict([('foo', 1), ('bar', 'y'), ('spam', 3), ('grok', 4)])
foo 1
bar y
spam 3
grok 4
{"foo": 1, "bar": "y", "spam": 3, "grok": 4}
OrderedDict
内部维护着一个根据键插入顺序排序的双向链表。一个 OrderedDict
的大小是一个普通字典的两倍,因为它内部维护着另外一个链表。仔细权衡一下是否使用 OrderedDict
带来的好处要大过额外内存消耗的影响。
1.8字典的运算
prices = {
'ACME': 45.23,
'AAPL': 612.78,
'IBM': 205.55,
'HPQ': 37.20,
'FB': 10.75
}
min_price = min(zip(prices.values(), prices.keys()))
print(min_price)
max_price = max(zip(prices.values(), prices.keys()))
print(max_price)
prices_sorted = sorted(zip(prices.values(), prices.keys()))
print(prices_sorted)
# 执行这些计算的时候,需要注意的是 zip() 函数创建的是一个只能访问一次的迭代器。
# 比如,下面的代码就会产生错误:
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 sequenc
console:
(10.75, 'FB')
(612.78, 'AAPL')
[(10.75, 'FB'), (37.2, 'HPQ'), (45.23, 'ACME'), (205.55, 'IBM'), (612.78, 'AAPL')]
1.9查找两字典的相同点
a = {
'x' : 1,
'y' : 2,
'z' : 3
}
b = {
'w' : 10,
'x' : 11,
'y' : 2
}
# Find keys in common
print(a.keys() & b.keys())
# Find keys in a that are not in b
print(a.keys() - b.keys())
# Find (key,value) pairs in common
print(a.items() & b.items())
# Make a new dictionary with certain keys removed
# 在a的基础上移除键值对'z'
c = {key:a[key] for key in a.keys() - {'z'}}
print(c)
console:
{'x', 'y'}
{'z'}
{('y', 2)}
{'x': 1, 'y': 2}
1.10 删除序列相同元素并保持顺序
怎样在一个序列上面保持元素顺序的同时消除重复的值?
# 如果序列上的值都是 hashable 类型,那么可以很简单的利用集合或者生成器来解决这个问题。比如:
def dedupe(items):
seen = set()
for item in items:
if item not in seen:
yield item
seen.add(item)
a = [1, 5, 2, 1, 9, 1, 5, 10]
print(list(dedupe(a)))
# 如果你想消除元素不可哈希(可修改的内建类型都是不可哈希比如 dict 类型)的序列中重复元素的话,
# 你需要将上述代码稍微改变一下,就像这样:
def dedupe2(items, key=None):
seen = set()
for item in items:
val = item if key is None else key(item)
# 此处key(item)为value值
if val not in seen:
yield item
seen.add(val)
b = [ {'x':1, 'y':2}, {'x':1, 'y':3}, {'x':1, 'y':2}, {'x':2, 'y':4}]
print(list(dedupe2(b, key=lambda d: (d['x'],d['y']))))
print(list(dedupe2(b, key=lambda d: d['x'])))
console:
[1, 5, 2, 9, 10]
[{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
[{'x': 1, 'y': 2}, {'x': 2, 'y': 4}]