数据结构和算法

1.1 解压序列赋值给多个变量

序列解包问题

有一个前提是变量数量要保持和序列数量一样。

>>> p = (4, 5)
>>> x, y = p
>>> x
4
>>> y
5

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

如果数量不匹配会产生异常,如果想丢弃某些值,可以用不常用的名字占位,然后丢弃即可。

实际上这种解包可以用在任何可迭代对象上,不仅仅是 list tuple,包括字符串,文件对象,迭代器生成器等。

1.2 解压可迭代对象赋值给多个变量

主题是在解包的时候利用 *操作符

比如这样

>>> 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']

注意 无论如何 phone_numbers 解压出来都是 list 类型,就算只有 0 个元素

另外还可以利用 *解包实现递归

>>> def sum(items):
... head, *tail = items
... return head + sum(tail) if tail else head

递归并不是 Python 擅长的

1.3 保留最后 N 个元素

比如保留最近 5 条历史记录

解决方案,利用 collections 库中的 deque,利用 maxlen 参数设置其最大长度

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)

deque 还实现了 appendleft appendright popleft popright 等。

在队列两端插入或删除元素时间复杂度都是 O(1) ,而在列表的开头插入或删除元素的时间复杂度为 O(N) 。

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

问题

集合里面,求最大或者最小的 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]

可以使用 key 参数,以支持更复杂的数据结构:

cheap = heapq.nsmallest(3, portfolio, key=lambda s: s['price'])

讨论

在底层实现里面,首先会先将集合数据进行堆排序后放入一个列表中:

>>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
>>> import heapq
>>> heapq.heapify(nums)
>>> nums
[-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8]

堆数据结构最重要的特征是 heap[0] 永远是最小的元素。并且剩余的元素可以很容易的通过调用 heapq.heappop() 方法得到, 该方法会先将第一个元素弹出来,然后用下一个最小的元素来取代被弹出元素(这种操作时间复杂度仅仅是O(log N),N是堆大小)。

>>> heapq.heappop(nums)
-4
>>> heapq.heappop(nums)
1
>>> heapq.heappop(nums)
2

注意

如果你仅仅想查找唯一的最小或最大(N=1)的元素的话,那么使用 min() 和 max() 函数会更快些。 类似的,如果N的大小和集合大小接近的时候,通常先排序这个集合然后再使用切片操作会更快点 ( sorted(items)[:N] 或者是 sorted(items)[-N:] )。

所以使用 heap 的场景是需要的 n 不是太大也不是太小的情况。

关于 堆排序更多的细节参考排序算法中堆排序的相关知识。

1.5 实现一个优先级队列

问题

怎样实现一个按优先级排序的队列? 并且在这个队列上面每次pop操作总是返回优先级最高的那个元素

解决方案

一种方式是使用堆,不过每个元素不仅仅包括了值,还包括了优先级,于是堆按照优先级进行处理操作:

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]

使用方式如下:

>>> 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)
>>> q.pop()
Item('bar')
>>> q.pop()
Item('spam')
>>> q.pop()
Item('foo')
>>> q.pop()
Item('grok')

注意到如果两个有着相同优先级的元素( foo 和 grok ),pop操作按照它们被插入到队列的顺序返回的。

讨论

函数 heapq.heappush() 和 heapq.heappop()分别在队列 _queue 上插入和删除第一个元素, 并且队列_queue保证第一个元素拥有最高优先级。 heappop() 函数总是返回”最小的”的元素,这就是保证队列pop操作返回正确元素的关键。 另外,push和pop操作时间复杂度为O(log N)

在上面代码中,队列包含了一个 (-priority, index, item) 的元组。 优先级为负数的目的是使得元素按照优先级从高到低排序。

index 变量的作用是保证同等优先级元素的正确排序。 通过保存一个不断增加的 index下标变量,可以确保元素按照它们插入的顺序排序。 而且,index 变量也在相同优先级元素比较的时候起到重要作用。

关于 Python 中的元祖排序,默认是按照元祖内元素排序先后进行排序的。即先比较元祖第一个元素,如果相同再比较第二个元素。如果前面的比较已经可以确定结果了,后面的比较操作就不会发生了。

如果想在多个线程中使用同一个队列,那么你需要增加适当的锁和信号量机制。

1.6 字典中的键映射多个值

问题

怎样实现一个键对应多个值的字典(也叫 multidict )?

解决方案

依旧是依靠字典一个键对应一个值,但是这个值可以是列表之类的,这样就实现了一个键对应多个值了。

你可以很方便的使用 collections 模块中的 defaultdict 来构造这样的字典。defaultdict 的一个特征是它会自动初始化每个 key 刚开始对应的值,所以你只需要关注添加元素操作了。比如:

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)

讨论

创建一个多值映射字典是很简单的。但是,如果你选择自己实现的话,那么对于值的初始化可能会有点麻烦

这样

d = {}
for key, value in pairs:
    if key not in d:
        d[key] = []
    d[key].append(value)

或者这样

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

如果使用 defaultdict 的话代码就更加简洁了:

d = defaultdict(list)
for key, value in pairs:
    d[key].append(value)

1.7 字典排序

问题

想要一个有序字典

解决方案

可以使用 collections 模块中的 OrderedDict类。 在迭代操作的时候它会保持元素被插入时的顺序:

from collections import OrderedDict

d = OrderedDict()
d['foo'] = 1
d['bar'] = 2
d['spam'] = 3
d['grok'] = 4
# Outputs "foo 1", "bar 2", "spam 3", "grok 4"
for key in d:
    print(key, d[key])

当你想要构建一个将来需要序列化或编码成其他格式的映射的时候, OrderedDict 是非常有用的。 比如,你想精确控制以JSON编码后字段的顺序

讨论

OrderedDict 内部维护着一个根据键插入顺序排序的双向链表。每次当一个新的元素插入进来的时候, 它会被放到链表的尾部。对于一个已经存在的键的重复赋值不会改变键的顺序。

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

得仔细权衡一下是否使用 OrderedDict 带来的好处要大过额外内存消耗的影响。

1.8 字典的运算

问题

如何对字典进行类似于求最大值、最小值等操作?

解决方案

有两种方式:

方式一,利用 zip 把 key 和 value 进行反转,然后利用 tuple 的比较操作就好。

prices = {
    'ACME': 45.23,
    'AAPL': 612.78,
    'IBM': 205.55,
    'HPQ': 37.20,
    'FB': 10.75
}

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() 函数创建的是一个只能访问一次的迭代器

方式二,利用 min 或 max 的 key 参数,但是这种方法只适合只想获得值,或者获得键的情况

min(prices.values()) # Returns 10.75
max(prices, key=lambda k: prices[k]) # Returns 'AAPL'

当多个实体拥有相同的值的时候,键会决定返回结果。 比如,在执行 min() 和 max() 操作的时候,如果恰巧最小或最大值有重复的,那么拥有最小或最大键的实体会返回。

1.9 查找两字典的相同点

问题

在两个字典中寻找相同点(比如相同的键,相同的值等)

解决方案

可以在 keys() 或者 items() 返回的结果上执行集合操作。

a = {
'x' : 1,
'y' : 2,
'z' : 3
}
b = {
'w' : 10,
'x' : 11,
'y' : 2
}
# 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) }

讨论

字典的 keys() 方法返回一个展现键集合的键视图对象。 键视图的一个很少被了解的特性就是它们也支持集合操作。

所以,如果你想对集合的键执行一些普通的集合操作,可以直接使用键视图对象而不用先将它们转换成一个set。

字典的 values() 方法也是类似,但是它并不支持集合操作。 某种程度上是因为值视图不能保证所有的值互不相同。

如果非要堆 对 values() 进行集合操作,可以先 set() 一下。

1.10 删除序列相同元素并保持顺序

问题

怎样在一个序列上面保持元素顺序的同时消除重复的值?

解决方案

如果序列上的值都是 hashable 类型

def dedupe(items):
    seen = set()
    for item in items:
        if item not in seen:
            yield item
        seen.add(item)

如果你想消除元素不可哈希(比如 dict 类型),可以这样

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}]

讨论

如果你仅仅就是想消除重复元素,通常可以简单的构造一个集合,但是这样会打乱原序列的顺序。

1.11 命名切片

问题

代码中出现了一大堆硬编码切片下标,影响程序可读性

解决方案

命名切片

SHARES = slice(20, 23)
PRICE = slice(31, 37)
cost = int(record[SHARES]) * float(record[PRICE])

讨论

内置的 slice() 函数创建了一个切片对象,slice() 接受三个参数,start stop step 可以被用在任何切片允许使用的地方。比如:

>>> items = [0, 1, 2, 3, 4, 5, 6]
>>> a = slice(2, 4)
>>> items[2:4]
[2, 3]
>>> items[a]
[2, 3]
>>> items[a] = [10,11]
>>> items
[0, 1, 10, 11, 4, 5, 6]
>>> del items[a]
>>> items
[0, 1, 4, 5, 6]

如果你有一个切片对象a,你可以分别调用它的 a.start , a.stop , a.step 属性来获取更多的信息。比如:

>>> a = slice(5, 50, 2)
>>> a.start
5
>>> a.stop
50
>>> a.step
2

另外,你还能通过调用切片的 indices(size) 方法将它映射到一个确定大小的序列上, 这个方法返回一个三元组 (start, stop, step) ,所有值都会被合适的缩小以满足边界限制, 从而使用的时候避免出现 IndexError 异常。比如:

>>> s = 'HelloWorld'
>>> a.indices(len(s))
(5, 10, 2)

1.12 序列中出现次数最多的元素

问题

怎样找出一个序列中出现次数最多的元素呢?

解决方案

collections.Counter 类就是专门为这类问题而设计的,而且有 most_common() 方法直接给了你答案。

words = [
    'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
    'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
    'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
    'my', 'eyes', "you're", 'under'
]
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 对象可以接受任意的由可哈希(hashable)元素构成的序列对象。 在底层实现上,一个 Counter 对象就是一个字典,将元素映射到它出现的次数上。

如何增加计数:

1.手动增加计数,可以简单的用加法:

word_counts[word] += 1

2.使用 update() 方法

>>> morewords = ['why','are','you','not','looking','in','my','eyes']
>>> word_counts.update(morewords)

Counter 实例一个鲜为人知的特性是可以跟数学运算操作或者说是集合操作相结合

>>> 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
>>> d = a - b
>>> d
Counter({'eyes': 7, 'the': 5, 'look': 4, 'into': 3, 'my': 2, 'around': 2,
"you're": 1, "don't": 1, 'under': 1})

1.13 通过某个关键字排序一个字典列表

问题

你有一个字典列表,你想根据某个或某几个字典字段来排序这个列表。

解决方案

通过使用 operator 模块的 itemgetter 函数,可以非常容易的排序这样的数据结构

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}
]

from operator import itemgetter
rows_by_fname = sorted(rows, key=itemgetter('fname'))

itemgetter() 函数也支持多个keys,在比较的时候按顺序进行比较

rows_by_lfname = sorted(rows, key=itemgetter('lname','fname'))

讨论

operator.itemgetter() 函数的参数可以是一个字典键名称, 一个整形值或者任何能够传入一个对象的 __getitem__() 方法的值。

itemgetter() 有时候也可以用 lambda 表达式代替,感觉itemgetter()就是一个闭包或者类

rows_by_fname = sorted(rows, key=lambda r: r['fname'])

使用 itemgetter() 方式会运行的稍微快点。因此如果你对性能要求比较高的话就使用 itemgetter() 方式。 这样的技术也同样适用于 min() 和 max() 等函数

1.14 排序不支持原生比较的对象

问题

你想排序类型相同的对象,但是他们不支持原生的比较操作。

解决方案

内置的 sorted() 函数有一个关键字参数 key ,可以传入一个 callable 对象给它

对于类对象一种方法是使用 operator.attrgetter() 另一种则是使用 lambda 函数

>>> from operator import attrgetter
>>> sorted(users, key=attrgetter('user_id'))
[User(3), User(23), User(99)]
class User:
    def __init__(self, user_id):
        self.user_id = user_id
    def __repr__(self):
        return 'User({})'.format(self.user_id)

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

讨论

attrgetter()函数通常会运行的快点,并且还能同时允许多个字段进行比较

1.15 通过某个字段将记录分组

问题

你有一个字典或者实例的序列,然后你想根据某个特定的字段比如 date 来分组迭代访问。

解决方案

itertools.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'},
    {'address': '2122 N CLARK', 'date': '07/03/2012'},
    {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
    {'address': '1060 W ADDISON', 'date': '07/02/2012'},
    {'address': '4801 N BROADWAY', 'date': '07/01/2012'},
    {'address': '1039 W GRANVILLE', 'date': '07/04/2012'},
]

from operator import itemgetter
from itertools import groupby

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

运行结果:

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'}
  {'date': '07/02/2012', 'address': '5645 N RAVENSWOOD'}
  {'date': '07/02/2012', 'address': '1060 W ADDISON'}
07/03/2012
  {'date': '07/03/2012', 'address': '2122 N CLARK'}
07/04/2012
  {'date': '07/04/2012', 'address': '5148 N CLARK'}
  {'date': '07/04/2012', 'address': '1039 W GRANVILLE'}

讨论

groupby() 函数扫描整个序列并且查找连续相同值(或者根据指定key函数返回值相同)的元素序列。 在每次迭代的时候,它会返回一个值和一个迭代器对象, 这个迭代器对象可以生成元素值全部等于上面那个值的组中所有对象。

有两点需要注意的地方:

1.groupby() 仅仅检查连续的元素,所以事先需要根据指定的字段将数据排序

2.如果只是想根据 date 字段将数据分组到一个大的数据结构中去,并且允许随机访问, 那么你最好使用 defaultdict() 来构建一个多值字典。

from collections import defaultdict
rows_by_date = defaultdict(list)
for row in rows:
    rows_by_date[row['date']].append(row)

这种方式会比先排序然后再通过 groupby() 函数迭代的方式运行得快一些。

1.16 过滤序列元素

问题

你有一个数据序列,想利用一些规则从中提取出需要的值或者是缩短序列

解决方案

最简单的过滤序列元素的方法就是使用列表推导。

为了避免对内存的过度消耗,可以考虑换用生成器表达式。

如果过滤规则过于复杂,则可以将过滤操作放在一个函数中,然后使用 filter() 进行过滤,注意 filter()返回一个迭代器。

讨论

列表和生成器表达式不仅可以过滤,还可以在过滤的过程中替换数据,可以配合 python 三元表达式效果更佳。

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

另外一个值得关注的过滤工具是 itertools.compress() 它以一个 iterable 对象和一个相对应的 Boolean 选择器序列作为输入参数。 然后输出 iterable 对象中对应选择器为 True 的元素.

addresses = [
    '5412 N CLARK',
    '5148 N CLARK',
    '5800 E 58TH',
    '2122 N CLARK',
    '5645 N RAVENSWOOD',
    '1060 W ADDISON',
    '4801 N BROADWAY',
    '1039 W GRANVILLE',
]
counts = [ 0, 3, 10, 4, 1, 7, 6, 1]

>>> from itertools import compress
>>> more5 = [n > 5 for n in counts]
>>> more5
[False, False, True, False, False, True, True, False]
>>> list(compress(addresses, more5))
['5800 E 58TH', '1060 W ADDISON', '4801 N BROADWAY']

1.17 从字典中提取子集

问题

根据一个字典构造新的字典

解决方案

最简单的方式是使用字典推导

p1 = {key: value for key, value in prices.items() if value > 200}

讨论

大多数情况下字典推导能做到的,通过创建一个元组序列然后把它传给 dict() 函数也能实现。

p1 = dict((key, value) for key, value in prices.items() if value > 200)

字典推导方式表意更清晰,并且实际上也会运行的更快些

1.18 映射名称到序列元素

问题

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

解决方案

命名元组:collections.namedtuple() 函数通过使用一个普通的元组对象来帮你解决这个问题。

>>> from collections import namedtuple
>>> Subscriber = namedtuple('Subscriber', ['addr', 'joined'])
>>> sub = Subscriber('jonesy@example.com', '2012-10-19')
>>> sub
Subscriber(addr='jonesy@example.com', joined='2012-10-19')
>>> sub.addr
'jonesy@example.com'
>>> sub.joined
'2012-10-19'

namedtuple 看起来像一个普通的类实例,但是它跟元组类型是可交换的,支持所有的普通元组操作,比如索引和解压。

讨论

命名元组的一个优点就是作为字典的替代,因为字典存储需要更多的内存空间。 如果你需要构建一个非常大的包含字典的数据结构,那么使用命名元组会更加高效。

但是,不像字典那样,命名元组是不可更改的

如果你真的需要改变属性的值,那么**可以使用命名元组实例的 _replace() 方法**, 它会创建一个全新的命名元组并将对应的字段用新的值取代。比如:

>>> s = s._replace(shares=75)
Stock(name='ACME', shares=75, price=123.45)

一个实践

_replace() 方法还有一个很有用的特性就是当你的命名元组拥有可选或者缺失字段时候, 它是一个非常方便的填充数据的方法。 你可以先创建一个包含缺省值的原型元组,然后使用 _replace() 方法创建新的值被更新过的实例。

from collections import namedtuple

Stock = namedtuple('Stock', ['name', 'shares', 'price', 'date', 'time'])

# Create a prototype instance
stock_prototype = Stock('', 0, 0.0, None, None)

# Function to convert a dictionary to a Stock
def dict_to_stock(s):
    return stock_prototype._replace(**s)

>>> a = {'name': 'ACME', 'shares': 100, 'price': 123.45}
>>> dict_to_stock(a)
Stock(name='ACME', shares=100, price=123.45, date=None, time=None)
>>> b = {'name': 'ACME', 'shares': 100, 'price': 123.45, 'date': '12/17/2012'}
>>> dict_to_stock(b)
Stock(name='ACME', shares=100, price=123.45, date='12/17/2012', time=None)

最后 如果要定义一个需要更新很多实例属性的高效数据结构,那么命名元组并不是你的最佳选择。 这时候你应该考虑定义一个包含 slots 方法的类

1.19 转换并同时计算数据

问题

你需要在数据序列上执行聚集函数(比如 sum() , min() , max() ), 但是首先你需要先转换或者过滤数据

解决方案

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

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

讨论

语法:

当生成器表达式作为一个单独参数传递给函数时候的巧妙语法(你并不需要多加一个括号)。 比如,下面这些语句是等效的:

s = sum((x * x for x in nums)) # 显式的传递一个生成器表达式对象
s = sum(x * x for x in nums) # 更加优雅的实现方式,省略了括号

使用一个生成器表达式作为参数会比先创建一个临时列表更加高效和优雅。

1.20 合并多个字典或映射

问题

合并多个字典或映射,之后还要对其进行操作,如何实现?

解决方案

一个非常简单的解决方案就是使用 collections 模块中的 ChainMap 类。

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

讨论

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

如果出现重复键,那么第一次出现的映射值会被返回

对于字典的更新或删除操作总是影响的是列表中第一个字典

new_child() parents() 以及 update() 方法

ChainMap 对于编程语言中的作用范围变量(比如 globals , locals 等)是非常有用的。 事实上,有一些方法可以使它变得简单:

>>> values = ChainMap()
>>> values['x'] = 1
>>> # Add a new mapping
>>> values = values.new_child()
>>> values['x'] = 2
>>> # Add a new mapping
>>> values = values.new_child()
>>> values['x'] = 3
>>> values
ChainMap({'x': 3}, {'x': 2}, {'x': 1})
>>> values['x']
3
>>> # Discard last mapping
>>> values = values.parents
>>> values['x']
2
>>> # Discard last mapping
>>> values = values.parents
>>> values['x']
1
>>> values
ChainMap({'x': 1})

作为 ChainMap 的替代,你可能会考虑使用 update() 方法将两个字典合并。但是它需要你创建一个完全不同的字典对象(或者是破坏现有字典结构)。ChainMap 使用原来的字典,它自己不创建新的字典。