1.1~1.2:解压可迭代对象赋值给多个变量(字符串,文件对象,迭代器和生成器)
- 若变量数量与可迭代对象的元素个数相同,直接使用赋值语句
>>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
>>> name, shares, price, date = data
>>> name
'ACME'
>>> date
(2012, 12, 21)
>>> name, shares, price, (year, mon, day) = data
>>> name
'ACME'
>>> year
2012
>>> mon
12
>>> day
21
- 若变量数量少于可迭代对象的元素个数,可以使用星号解压语法(注意带有*表达式的变量是列表类型)
>>> record = ('Dave', 'dave@example.com', '773-555-1212', '847-555-1212')
>>> name, email, *phone_numbers = record
>>> name
'Dave'
>>> email
'dave@example.com'
>>> phone_numbers
['773-555-1212', '847-555-1212']
- 若只想解压一部分,丢弃其他的值,可以使用任意变量名去占位,例如_,ign(ignore)
>>> record = ('ACME', 50, 123.45, (12, 18, 2012))
>>> name, *_, (*_, year) = record
>>> name
'ACME'
>>> year
2012
1.3:保留最后 N 个元素
一、预备知识:
1. collections模块:
- namedtuple:自定义一个tuple对象,并且可以用属性来索引元素
# namedtuple('名称', [属性list]):
>>> from collections import namedtuple
>>> point = namedtuple('Point', ['x', 'y'])
>>> p = point(1, 2)
>>>#p=point._make([1,2])也可以,注意得传入List
>>> p.x
1
>>> p.y
2
>>> isinstance(p, point)
True
>>> isinstance(p, tuple)
True
- deque:实现高效插入和删除操作的双向列表,除了实现list的append()和pop()外,还支持appendleft()和popleft()。在deque两端插入或删除元素时间复杂度都是 O(1) ,而在list的开头插入或删除元素的时间复杂度为 O(N)。
>>> from collections import deque
>>> q = deque(['a', 'b', 'c'])
>>> q.append('x')
>>> q.appendleft('y')
>>> q
deque(['y', 'a', 'b', 'c', 'x'])
- defaultdict(1.6节):解决引用dict中不存在的key时抛出异常的问题。参数指定key对应的value的数据结构(详见1.6);也可以传入简单的匿名函数指定key不存在时的返回值
>>> from collections import defaultdict
>>> dd = defaultdict(lambda: 'N/A')
>>> dd['key1'] = 'abc'
>>> dd['key1'] # key1存在
'abc'
>>> dd['key2'] # key2不存在,返回默认值
'N/A'
- OrderedDict(1.7节):解决使用dict时key无序的问题
>>> from collections import OrderedDict
>>> d = dict([('a', 1), ('b', 2), ('c', 3)])
>>> d # dict的Key是无序的
{'a': 1, 'c': 3, 'b': 2}
>>> od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
>>> od # OrderedDict的Key是有序的
OrderedDict([('a', 1), ('b', 2), ('c', 3)])
- Counter:一个简单的计数器(实际上是dict的一个子类)
>>> from collections import Counter
>>> c = Counter()
>>> for ch in 'programming':
... c[ch] = c[ch] + 1
...
>>> c
Counter({'g': 2, 'm': 2, 'r': 2, 'a': 1, 'i': 1, 'o': 1, 'n': 1, 'p': 1})
二、开始实现:
- 使用 deque(maxlen=N) 构造函数会新建一个固定大小的队列。当新的元素加入并且这个队列已满的时候, 最老的元素会自动被移除掉。
>>> q = deque(maxlen=3)
>>> q.append(1)
>>> q.append(2)
>>> q.append(3)
>>> q
deque([1, 2, 3], maxlen=3)
>>> q.append(4)
>>> q
deque([2, 3, 4], maxlen=3)
>>> q.append(5)
>>> q
deque([3, 4, 5], maxlen=3)
1.4 查找最大或最小的 N 个元素:
一、预备知识:
- heapq模块的相关函数:
- heappush(heap, x):将x压入堆中
- heappop(heap):从堆中弹出最小的元素
- heapify(heap):让列表具备堆特征
- heapreplace(heap, x):弹出最小的元素,并将x压入堆中
- nlargest(n, iter):返回iter中n个最大的元素
- nsmallest(n, iter):返回iter中n个最小的元素
二、实现查找:
- 若想查找唯一的最小或最大(N=1)的元素的话,直接使用 min() 和 max() 函数
- 若N的大小和集合大小接近的时候,直接排序这个集合然后再使用切片操作 ( sorted(items)[:N] 或者是 sorted(items)[-N:] )
- 若查找的元素个数相对比较小的时候,可以使用heapq模块的函数 nlargest() 和 nsmallest()
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]
#两个函数都能接受一个关键字参数,用于更复杂的数据结构中
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}
]
#在对每个元素进行对比的时候,会以 price 的值进行比较
cheap = heapq.nsmallest(3, portfolio, key=lambda s: s['price'])
expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price'])
- 也可以通过heapq模块的heapify(heap)函数实现,该函数先将集合数据进行堆排序(位置i处的元素总是大于位置i // 2处的元素)后放入一个列表中
>>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
>>> import heapq
>>> heap = list(nums)
>>> heapq.heapify(heap)
>>> heap
[-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8]
>>>
- heap[0] 永远是最小的元素。并且剩余的元素可以很容易的通过调用 heapq.heappop() 方法得到, 该方法会先将第一个元素弹出来,然后用下一个最小的元素来取代被弹出元素(这种操作时间复杂度仅仅是 O(log N),N 是堆大小)。 比如,如果想要查找最小的 3 个元素,你可以这样做:
>>> heapq.heappop(heap)
-4
>>> heapq.heappop(heap)
1
>>> heapq.heappop(heap)
2
1.5:实现一个优先级队列
一、预备知识:
- _repr _ VS _str _:_repr _返回变量本身直接显示的结果,一般表示一个对象来源的类以及继承关系,方便程序员们了解这个对象; _str _用于表示print输出的结果(print() 方法会调用 _str _ 方法)
>>> class A:
def __str__(self):
return "__str__"
def __repr__(self):
return "__repr__"
>>> a1 = A()
>>> a1
__repr__
>>> print(a1)
__str__
- %r VS {!r}:效果相同,都用于格式化字符串,后者只能用于foramt
>>> a = '123'
>>> b = 'hello, {!r}'.format(a)
>>> b
"hello, '123'"
>>> a = '123'
>>> b = 'hello, %r' % a
>>> b
"hello, '123'"
>>> b = 'hello, !r' % '123'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: not all arguments converted during string formatting
>>>
二、开始实现:
- 先假定Item 实例是不支持排序的:
>>> class Item:
... def __init__(self, name):
... self.name = name
... def __repr__(self):
... return 'Item({!r})'.format(self.name)
>>> a = Item('foo')
>>> b = Item('bar')
>>> a < b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Item() < Item()
>>>
- 考虑使用元组 (priority, item) ,这样两个元素的优先级不同就能比较。 但是如果两个元素优先级一样的话,那么比较操作就会跟之前一样出错:
>>> a = (1, Item('foo'))
>>> b = (5, Item('bar'))
>>> a < b
True
>>> c = (1, Item('grok'))
>>> a < c
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Item() < Item()
>>>
- 通过引入另外的 index 变量组成三元组 (priority, index, item) ,解决优先级相同的问题:如果优先级相同,则比较index
>>> a = (1, 0, Item('foo'))
>>> b = (5, 1, Item('bar'))
>>> c = (1, 2, Item('grok'))
>>> a < b
True
>>> a < c
True
>>>
- 最终结合heapq模块创建优先级队列:
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
def pop(self):
return heapq.heappop(self._queue)[-1]
>>> q = PriorityQueue()
>>> q.push(Item('foo'), 1)
>>> q.push(Item('bar'), 5)
>>> q.push(Item('spam'), 4)
>>> q.push(Item('grok'), 1)
>>> q.pop()
Item('bar')
>>> q.pop()
Item('spam')
>>> q.pop()
Item('foo')
>>> q.pop()
Item('grok')
>>>
- heappop() 函数总是返回”最小的”的元素,这就是保证队列pop操作返回正确元素的关键。
- 队列包含了一个 (-priority, index, item) 的元组。 优先级为负数的目的是使得元素按照优先级从高到低排序。
- index 变量的作用是保证同等优先级元素的正确排序。
1.6:字典中的键映射多个值
- 直接构造:
d = {
'a' : [1, 2, 3],
'b' : [4, 5]
}
e = {
'a' : {1, 2, 3},
'b' : {4, 5}
}
- 利用函数构造:
d = {}
for key, value in pairs:
if key not in d:
d[key] = []
d[key].append(value)
- 利用collections 模块中的 defaultdict构造:
d = defaultdict(list)
for key, value in pairs:
d[key].append(value)
- defaultdict 的一个特征是它会自动初始化每个 key 刚开始对应的值,即避免了寻找不存在的key时引发的异常,会自动为将要访问的键(就算目前字典中并不存在这样的键)创建映射实体。
from collections import defaultdict
#注意往list添加元素是append
d = defaultdict(list)
d['a'].append(1)
d['a'].append(2)
d['b'].append(4)
#注意往set添加元素是add
d = defaultdict(set)
d['a'].add(1)
d['a'].add(2)
d['b'].add(4)
- 选择使用列表还是集合取决于你的实际需求。如果你想保持元素的插入顺序就应该使用列表, 如果想去掉重复元素就使用集合(并且不关心元素的顺序问题)。
- 也可以在一个普通的字典上使用 setdefault() 方法来代替,第一个参数表示要寻找的key,第二个参数表示key不存在时的返回值。例如:
d = {} # 一个普通的字典
d.setdefault('a', []).append(1)
d.setdefault('a', []).append(2)
d.setdefault('b', []).append(4)
1.7:字典排序
- collections 模块中的 OrderedDict 类。 在迭代操作的时候它会保持元素被插入时的顺序,示例如下:
from collections import OrderedDict
d = OrderedDict()
d['foo'] = 1
d['bar'] = 2
d['spam'] = 3
d['grok'] = 4
# OrderedDict([('foo', 1), ('bar', 2), ('spam', 3), ('grok', 4)])
for key in d:
print(key, d[key])
- 当你想要构建一个将来需要序列化或编码成其他格式的映射的时候, 比如想精确控制以 JSON 编码后字段的顺序,这时OrderedDict 是非常有用的。
>>> import json
>>> json.dumps(d)
'{"foo": 1, "bar": 2, "spam": 3, "grok": 4}'
>>>
- OrderedDict之所以有这个功能,是因为其内部维护着一个根据键插入顺序排序的双向链表。但是副作用即是一个 OrderedDict 的大小是一个普通字典的两倍。
1.8:字典的运算:
- 考虑下面的股票名和价格映射字典:
prices = {
'ACME': 45.23,
'AAPL': 612.78,
'IBM': 205.55,
'HPQ': 37.20,
'FB': 10.75
}
- 如果你在一个字典上执行普通的数学运算,你会发现它们仅仅作用于键,而不是值。
min(prices) # Returns 'AAPL'
max(prices) # Returns 'IBM'
- 尝试着使用字典的 values() 方法来解决这个问题:
min(prices.values()) # Returns 10.75
max(prices.values()) # Returns 612.78
3.但是如果想获得键的信息呢?也简单:
min(prices, key=lambda k: prices[k]) # Returns 'FB'
max(prices, key=lambda k: prices[k]) # Returns 'AAPL'
- 但这个想获得最小值的话,又得执行一次查找操作:
min_value = prices[min(prices, key=lambda k: prices[k])]
- 最完美的做法是什么呢?可以使用 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')
类似的,可以使用 zip() 和 sorted() 函数来排列字典数据:
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)) # OK
print(max(prices_and_names)) # ValueError: max() arg is an empty sequence
此外,当多个实体拥有相同的值的时候,键会决定返回结果。
>>> prices = { 'AAA' : 45.23, 'ZZZ': 45.23 }
>>> min(zip(prices.values(), prices.keys()))
(45.23, 'AAA')
>>> max(zip(prices.values(), prices.keys()))
(45.23, 'ZZZ')
>>>
1.9:寻找两字典的相同点
- 考虑下面两个字典:
a = {
'x' : 1,
'y' : 2,
'z' : 3
}
b = {
'w' : 10,
'x' : 11,
'y' : 2
}
- 为了寻找两个字典的相同点,可以简单的在两字典的 keys() 或者 items() 方法返回结果上执行集合操作。比如:
#返回的是数据类型是set
# Find keys in common
a.keys() & b.keys() # { 'x', 'y' }
# Find keys in a that are not in b
a.keys() - b.keys() # { 'z' }
# Find (key,value) pairs in common
a.items() & b.items() # { ('y', 2) }
这些操作也可以用于修改或者过滤字典元素。 比如,假如你想以现有字典构造一个排除几个指定键的新字典。 下面利用字典推导来实现这样的需求:
# Make a new dictionary with certain keys removed
c = {key:a[key] for key in a.keys() - {'z', 'w'}}
# c is {'x': 1, 'y': 2}
- 注意:
- 字典的 keys() 方法返回一个展现键集合的键视图对象。 键视图的一个很少被了解的特性就是它们支持集合操作,比如集合并、交、差运算。
- 字典的 items() 方法返回一个包含 (键,值) 对的元素视图对象。 这个对象同样也支持集合操作,并且可以被用来查找两个字典有哪些相同的键值对。
- 尽管字典的 values() 方法也是类似,但是它并不支持这里介绍的集合操作。如果你硬要在值上面执行这些集合操作的话,你可以先将值集合转换成 set,然后再执行集合运算就行了。
1.10:删除序列相同元素并保持顺序
一、预备知识:
- hashable(可哈希的)的理解:暂时可以不严谨的理解为hashable ≈ imutable,即可哈希 ≈ 不可变。
- list、set和dictionary 都是可改变的,比如可以通过list.append(),set.remove(),dict[‘key’] = value对其进行修改,所以它们都是不可哈希的;
- tuple和string是不可变的,只可以做复制或者切片等操作,所以它们就是可哈希的。
二、开始实现:
- 仅仅想消除重复元素,可以构造一个集合,但是这种方法不能维护元素的顺序,生成的结果中的元素位置被打乱。:
>>> a
[1, 5, 2, 1, 9, 1, 5, 10]
>>> set(a)
{1, 2, 10, 5, 9}
>>>
- 那么如何实现删除相同元素的同时保持顺序呢?若序列上的值都是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]
>>> list(dedupe(a)) #dedupe(a)是一个生成器,转换为list可以使其展开
[1, 5, 2, 9, 10]
>>>
- 若序列上的值不是hashable 类型,则将他们转换为hashable 类型:
def dedupe(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)
#key参数指定了一个函数,将序列元素转换成 hashable 类型
>>> 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}]
>>>