《Python Cookbook 第3版》知识点整理(一):算法与数据结构(含代码注释)

大家好,我是瓜瓜。 本专栏将记录本人系统学习《Python Cookbook (第3版)》的笔记,整理涉及的编程问题及代码,并添加注释,方便查阅。有python语法基础的同学,可以将其作为一套练习题来进阶学习。

1. 将序列赋值给多个变量

问题描述: 现有一个元组或者列表,如何将里面的元素解压,并同时赋值给多个变量?
解决方案: 通过赋值语句即可取出序列中的一个个元素。
示例:

# nums 为元组序列
nums = (4, 5)
# 将序列中的元素赋值给x,y
x, y = nums
print(x, y)

输出:

x = 4, y = 5


data = ['ACME', 50, 91.1, (2012, 12, 21)]
name, shares, price, date = data
print(name)
print(date)
year, mon, day = date
print(year)

输出

ACME
(2012, 12, 21)
2012


讨论: 这种解压赋值可以用在任何可迭代对象上,包括字符串,文件对象,迭代器和生成器。
示例:

s = "Hello"
a, b, c, d, e = s
print(a)
print(b)
print(e)

输出:

H
e
o


另外,如果想只解压一部分,可以使用任意变量名去占位,使用的时候将其丢弃。需要注意,这些占位变量名在其他地方没有使用到。
示例:

data = ['ACME', 50, 91.1, (2012, 12, 21)]  
# 不需要使用的变量用'_'来占位           
_, shares, price, _ = data                            
print("shares=%d, price=%d" % (shares,  price))       

输出:

shares=50, price=91

2. 星号表达式解压可迭代对象的部分元素

问题描述: 在使用赋值的方法解压可迭代对象元素的时候,如果元素超过变量个数,将抛出一个错误。那么,如何解压出部分的指定元素?
解决方案: 如果序列的元素个数少,可以使用占位变量的赋值方法进行解压,但如果序列的元素个数较多,则该方法变得十分繁琐,这时候星号表达式就派上用场了。
示例:

def avg(nums):
    return sum(nums) / len(nums)

def drop_fist_last(grades):
    # 使用星号表达式让middle包含了中间的元素,去掉了第一个和最后一个分数
    first, *middle, last = grades
    return avg(middle)

arr = [77, 79, 81, 83, 83, 86, 91, 93, 94, 96]
avg_grade = drop_fist_last(arr)
print("平均分=%d" % avg_grade)

输出:

平均分=86


另外一种情况,假设现有一些用户的记录列表,每条记录包含一个名字、邮件,接着是不确定数量的电话号码。则可以用以下方式分解这些记录。
示例:

record = ('Dave', 'dave@example.com', '773-555-1212', '847-555-1212')
name, email, *phone_numbers = record
print("name:", name)
print("email:", email)
print("phone_numbers:", phone_numbers)

输出:

name: Dave
email: dave@example.com
phone_numbers: [‘773-555-1212’, ‘847-555-1212’]

注意,虽然原来的record序列是元组,但解压出来的phone_numbers永远是列表类型,不管解压出来的电话号码数量是多少(包括0个)。


星号表达式也能用在列表的开始部分。比如,你有一个公司前8个月销售数据的序列,但你想看最近一个月的数据和前面7个月平均值的对比,可以这样做:

sales_record =  [10, 8, 7, 1, 9, 5, 10, 3]
*trailing_qtrs, current_qtr = sales_record
trailing_avg = sum(trailing_qtrs) / len(trailing_qtrs)
print(trailing_qtrs)
print("trailing_avg = %.2f" % trailing_avg)
print("current_qtr =", current_qtr)

输出:

[10, 8, 7, 1, 9, 5, 10]
trailing_avg = 7.14
current_qtr = 3


当迭代序列的元素是可变长的元组,星号表达式可以方便地解压这些元组元素。例如,下面对带有标签的元组序列进行处理:

# record的元素是长度不一致的元组,第一个元素作为标签进行识别
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)

输出:

foo 1 2
bar hello
foo 3 4


星号表达式用于字符串操作:

#分割字符串并获取感兴趣的内容
line = 'nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false'
#list.split(':')的作用是将字符串按照':'分割,分割后的子串保存在一个列表中
uname, *fields, homedir, sh = line.split(':')
print("uname:", uname)
print("homedir:", homedir)
print("sh:", sh)

输出:

uname: nobody
homedir: /var/empty
sh: /usr/bin/false


有时候,想解压一些元素并丢弃他们,可以用星号表达式结合占位变量:

record = ('ACME', 50, 123.45, (12, 18, 2012))
name, *_, (*_, year) = record
print("name:", name)
print("year:", year)

输出:

name: ACME
year: 2012


如果你有有一个列表,可以用方便地用星号表达式分割为前后两部分:

items = [1, 10, 7, 4, 5, 9]
head, *tail = items
print(head)
print(tail)

输出:

1
[10, 7, 4, 5, 9]


利用这种分割语法,可以巧妙地实现元素相加的递归算法:

items = [1, 10, 7, 4, 5, 9]
def sum(items):
    head, *tail = items
    return head + sum(tail) if tail else head
    # 相当于每次都是用第一个元素加上剩下元素的和,以分解为一个个子问题
print(sum(items))

输出:

36

3. 队列collections.deque类的使用

问题描述: 在迭代操作或者其他操作的时候,怎样保留最后有限几个元素的历史记录?
解决方案: 使用collections.deque
示例1:

from collections import deque
q = deque(maxlen=5)
for i in range(5):
    q.append(i)
print(q)
# 当q再加入一个元素,则第一个元素被丢弃,队列保持最大长度3
q.append(5)
print(q)
# 继续加入元素
q.append(6)
print(q)

输出:

deque([0, 1, 2, 3, 4], maxlen=5)
deque([1, 2, 3, 4, 5], maxlen=5)
deque([2, 3, 4, 5, 6], maxlen=5)


如果不指定最大队列大小,那么可以得到一个无限大的队列,你可以在队列两端执行添加和弹出的操作:

from collections import deque

q = deque()
q.append(1)
q.append(2)
q.append(3)
print(q)
# 左端加入4
q.appendleft(4)
# 右端弹出
q.pop()
print(q)
# 左端弹出
print(q.popleft())

输出:

deque([1, 2, 3])
deque([4, 1, 2])
4


示例2:
对一个多行文本,进行匹配,寻找含有pattern的行,并返回匹配所在行的最后N行。

from collections import deque

def search(lines, pattern, history=5):
    previous_lines = deque(maxlen=history)
    #迭代处理每一行
    for li in lines:
        # 如果该行包含pattern,则返回该行,和历史队列
        if pattern in li:
            yield li, previous_lines
        previous_lines.append(li) # 位置***

# Example use on a file
if __name__ == '__main__':
    with open(r'./somefile.txt') as f:
        for line, prevlines in search(f, 'Python', 5):
            # 打印历史队列
            # 注意第一次循环返回line为第一行,而prevlines还是空的,跳过下面的内层循环。
            # 第二次,从位置***开始执行,先append第一行,由于接下来的几行没包含pattern,所以只是把这几行放入previous
            # 直到遇到下一个pattern才执行yield,然后开始打印历史和当前行
            for pline in prevlines:
                print(pline, end='')
            #打印当前行
            print(line, end='')
            print('-' * 20)

"""
somefile.txt的内容如下:
Python is powerful... and fast;
plays well with others;
runs everywhere;
is friendly & easy to learn;
is Open.
These are some of the reasons people who use Python would rather not use anything else.

Python can be easy to pick up whether you're a first time programmer
or you're experienced with other languages.
The following pages are a useful first step to get on your way
writing programs with Python!

The community hosts conferences and meetups, collaborates on code,
and much more. Python's documentation will help you along the way,
and the mailing lists will keep you in touch.

Conferences and Workshops
Python Documentation
Mailing Lists and IRC channels
"""

输出:

Python is powerful… and fast;
--------------------
Python is powerful… and fast;
plays well with others;
runs everywhere;
is friendly & easy to learn;
is Open.
These are some of the reasons people who use Python would rather not use anything else.
--------------------
runs everywhere;
is friendly & easy to learn;
is Open.
These are some of the reasons people who use Python would rather not use anything else.

Python can be easy to pick up whether you’re a first time programmer
--------------------
These are some of the reasons people who use Python would rather not use anything else.

Python can be easy to pick up whether you’re a first time programmer
or you’re experienced with other languages.
The following pages are a useful first step to get on your way
writing programs with Python!
--------------------
or you’re experienced with other languages.
The following pages are a useful first step to get on your way
writing programs with Python!

The community hosts conferences and meetups, collaborates on code,
and much more. Python’s documentation will help you along the way,
--------------------
The community hosts conferences and meetups, collaborates on code,
and much more. Python’s documentation will help you along the way,
and the mailing lists will keep you in touch.

Conferences and Workshops
Python Documentation
--------------------

我们在写查询元素的代码时,通常会使用包含 yield 表达式的生成器函数,也就是如上示例代码中的那样。这样可以将搜索过程代码和使用搜索结果代码解耦。


【注】关于生成器
生成器是一种迭代器(但迭代器不一定是生成器)。
使用yield关键字可以定义一个生成器。
生成器每次产生一个值后,该函数将被冻结,不在执行未完成的语句,直到被再次唤醒后在从刚才的位置继续执行,产生下一个值,如此循环往复,每次只计算生成一个值。
yield通常配合for循环使用,为for循环源源不断的每次一个的输出值。

生成器的一个例子:

def gen(n):
    for i in range(n):
        yield 2 ** i
for i in gen(5):
    print(i, ' ', end='')

输出:

1 2 4 8 16

4. heapq模块查找最大或最小的N个元素

问题描述: 怎样从列表中获取最大或者最小的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))

输出:

[42, 37, 23]
[-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'])
# 按照price的值,取最大的三个元素
expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price'])
print(cheap)
print(expensive)

输出:

[{‘name’: ‘YHOO’, ‘shares’: 45, ‘price’: 16.35}, {‘name’: ‘FB’, ‘shares’: 200, ‘price’: 21.09}, {‘name’: ‘HPQ’, ‘shares’: 35, ‘price’: 31.75}]
[{‘name’: ‘AAPL’, ‘shares’: 50, ‘price’: 543.22}, {‘name’: ‘ACME’, ‘shares’: 75, ‘price’: 115.65}, {‘name’: ‘IBM’, ‘shares’: 100, ‘price’: 91.1}]

以上代码在对每个元素进行对比的时候,会以price的值进行比较。匿名函数lambda s: s[‘price’] 的意思是以每个元素s作为输入,返回s[‘price’],即每个元素中price的值。


讨论:堆
堆是一个二叉树,对于最小堆,它的每个父节点的值都只会小于或等于所有孩子节点(的值)。
heapq有两种方式创建堆, 一种是使用一个空列表,然后使用heapq.heappush()函数把值加入堆中,另外一种就是使用heap.heapify(list)转换列表成为堆结构。
建堆方法一:

nums = [7, 2, -1, 8, 5, -4, 4, 9]
heap = []
for num in nums:
    heapq.heappush(heap, num)
# 建堆后,heap[0]永远是最小的
print(heap)
# out: [-4, 5, -1, 8, 7, 2, 4, 9]
# 依次打印当前最小的值
print(heapq.heappop(heap))
print(heapq.heappop(heap))
print(heapq.heappop(heap))

输出:

[-4, 5, -1, 8, 7, 2, 4, 9]
-4
-1
2

建堆方法二:

nums = [7, 2, -1, 8, 5, -4, 4, 9]
# 将nums变成最小堆
heapq.heapify(nums)
print(nums[0])
print([heapq.heappop(nums) for _ in range(len(nums))])

输出:

-4
[-4, -1, 2, 4, 5, 7, 8, 9]


【注】

  1. 当查找的元素个数相对较小时,函数 nlargest() 和 nsmallest() 是比较合适的。
  2. 当查找的元素个数N与集合大小接近时,先排序这个集合然后再使用切片会快一些,sorted(items)[: N] 或者 sorted(items)[-N :]
  3. 如果只需要找到唯一的最大最小值,用max() 或者 min() 即可。

5. 利用heapq模块实现一个优先级队列

问题描述: 实现一个按优先级排序的队列,并且每次pop操作总是返回优先级最高的那个元素
解决方案: 利用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('foo', 1)
q.push('bar', 5)
q.push('spam', 4)
q.push('grok', 1)
for i in range(4):
    print(q.pop())

输出:

bar
spam
foo
grok

6. 利用collections.defaultdict将字典中的键映射多个值

问题描述: 怎么实现一个键对应多个值的字典?
解决方案: 将多个值存放在列表或者集合等容器中
示例一:

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)

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

如果手动实现,会稍微麻烦点:

values = [95, 88, 92, 94, 77, 55, 67, 78]
names = ['张三', '张三', '李四', '王五', '赵六', '王五', '张三', '李四']

dic = {}
for key, value in zip(names, values):
    if key not in dic:
        dic[key] = []
    dic[key].append(value)
print(dic)

输出:

{‘张三’: [95, 88, 67], ‘李四’: [92, 78], ‘王五’: [94, 55], ‘赵六’: [77]}

利用defaltdict,代码比较简洁:

dic = defaultdict(list)
for key, value in zip(names, values):
    dic[key].append(value)
print(dic)

7. 利用 collections.OrderedDict 类控制字典中元素的顺序

问题描述: 如何创建一个字典,并且迭代的时候按照插入的顺序输出?
解决方案: 利用collections中的OrdereDict类
示例:

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

输出:

foo 1
bar 2
spam 3
grok 4

如果想精确控制以JSON编码后字段的顺序,可以先使用OrdereDict构建数据,再进行编码:

import json
data = json.dumps(d)
print(data)

输出:

{“foo”: 1, “bar”: 2, “spam”: 3, “grok”: 4}

8. 字典的运算

问题描述: 怎样在数据字典中执行某些计算操作(求最小值、最大值、排序等等)?
示例1:
查找最大最小值

prices = {
        'ACME': 45.23,
        'AAPL': 612.78,
        'IBM': 205.55,
        'HPQ': 37.20,
        'FB': 10.75
    }
# 为了按照值来查找,需要使用zip()函数现将键和值反转过来。
min_price = min(zip(prices.values(), prices.keys()))
max_price = max(zip(prices.values(), prices.keys()))
print(min_price)
print(max_price)

输出:

(10.75, ‘FB’)
(612.78, ‘AAPL’)


示例2:
按照值,从小到大排序

prices_sorted = sorted(zip(prices.values(), prices.keys()))

输出:

[(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

如果直接在一个字典上执行普通的数学运算,得到的结果是按照键来算的,而不是值

min(prices) # 返回 'AAPL'
max(prices) # 返回 'IBM'

使用匿名函数可以让运算按照值来执行:

min(price, key=lambda k: prices[k]) # 返回 'FB'
max(price, key=lambda k: prices[k]) # 返回 'AAPL'

9. 查找两个字典的相同点

问题描述: 怎样在两个字典中寻找相同的键、相同的值等等?
解决方案:
考虑下面两个字典:

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

    b = {
        'w': 10,
        'x': 11,
        'y': 2
    }

示例1:

# 找相同的键
print(a.keys() & b.keys())  # { 'x', 'y' }
# 在a中但不在b中的键
print(a.keys() - b.keys())  # { 'z' }
# 找相同的键-值对
print(a.items() & b.items())  # { ('y', 2) }

示例2:

c = {key:a[key] for key in a.keys() - {'z', 'w'}}

输出:

{‘x’: 1, ‘y’: 2}

10. 去掉序列的相同元素并保持顺序

问题描述: 怎样在一个序列上保持元素顺序的同时消除重复的值?
解决方案: 如果序列是可哈希的(可以作为字典的键或者set的成员),那么可以简单得利用集合+生成器来解决:

def dedupe(items):
    seen = set()
    for item in items:
        # 不在集合seen里面的才返回,以消除重复
        if item not in seen:
            yield item
            seen.add(item)
        
a= [1, 5, 2, 1, 9, 1, 5, 10]
print(list(dedupe(a)))

输出:

[1, 5, 2, 9, 10]


当序列中的元素为不可哈希类型时(比如列表、字典、集合),则可以看元素里面的value是否重复:

def dedupe2(items, key=None):
    """元素不是hashable的时候"""
    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)
            
a = [ {'x':1, 'y':2}, {'x':1, 'y':3}, {'x':1, 'y':2}, {'x':2, 'y':4}]
# 去掉x和y的值都重复的
res1 = list(dedupe2(a, key=lambda d: (d['x'], d['y'])))
print('res1:', res1)
# 去掉x的值重复的
res2 = list(dedupe2(a, key= lambda d: d['x']))
print('res2:', res2)

输出:

res1: [{‘x’: 1, ‘y’: 2}, {‘x’: 1, ‘y’: 3}, {‘x’: 2, ‘y’: 4}]
res2: [{‘x’: 1, ‘y’: 2}, {‘x’: 2, ‘y’: 4}]

11. slice() 命名切片下标

问题描述: 当出现一大堆切片下标,代码难以阅读和维护。
解决方案: 利用slice()函数给切片下标命名
示例1:

# reord中的[20:23]和[31:37]位置分别记录了股份数量和单价,
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])
print(cost)

输出:

51325.0


示例2:

items = [0, 1, 2, 3, 4, 5, 6]
a = slice(2, 4)
print(items[a])

items[a] = [10,11]
print(items)

del items[a]
print(items)

输出:

[2, 3]
[0, 1, 10, 11, 4, 5, 6]
[0, 1, 4, 5, 6]


对于切片对象a,可以调用其a.start,a.stop, a.step属性来获取更多信息:

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

输出:

5
50
2


利用切片对象的indices(size)方法压缩长度:

a = slice(5, 50, 2)
s = 'HelloWorld'
print(a.indices(len(s))) #压缩为(5, 10, 2)
for i in range(*a.indices(len(s))):
    print(s[i])

输出:

(5, 10, 2)
W
r
d

12. collections.Counter类统计序列中元素出现的次数

问题描述: 怎样找出一个序列中出现次数最多的元素?
解决方案: collections.Counter类
示例1:

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)

输出:

[(‘eyes’, 8), (‘the’, 5), (‘look’, 4)]
可以将Counter对象看成一个字典,将元素映射到它出现的次数上:

num1 = word_counts['my']
num2 = word_counts['eyes']
print(num1, num2)

输出:

3 8


示例2:
如果想手动增加计数,可以简单地用加法:

morewords = ['why','are','you','not','looking','in','my','eyes']
for word in morewords:
    word_counts[word] += 1
print(word_counts['eyes'])

输出:

9
或者使用update()方法:

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

两个Counter示例可以进行加减运算:

a = Counter(words)
b = Counter(morewords)
c = a + b
d = a - b
print('a:', a)
print('b:', b)
print('c:', c)
print('d:', d)

输出:

a: Counter({‘eyes’: 8, ‘the’: 5, ‘look’: 4, ‘into’: 3, ‘my’: 3, ‘around’: 2, ‘not’: 1, “don’t”: 1, “you’re”: 1, ‘under’: 1})
b: Counter({‘why’: 1, ‘are’: 1, ‘you’: 1, ‘not’: 1, ‘looking’: 1, ‘in’: 1, ‘my’: 1, ‘eyes’: 1})
c: Counter({‘eyes’: 9, ‘the’: 5, ‘look’: 4, ‘my’: 4, ‘into’: 3, ‘not’: 2, ‘around’: 2, “don’t”: 1, “you’re”: 1, ‘under’: 1, ‘why’: 1, ‘are’: 1, ‘you’: 1, ‘looking’: 1, ‘in’: 1})
d: Counter({‘eyes’: 7, ‘the’: 5, ‘look’: 4, ‘into’: 3, ‘my’: 2, ‘around’: 2, “don’t”: 1, “you’re”: 1, ‘under’: 1})

13. 利用opeator.itemgetter()函数根据某个关键字排序列表

问题描述: 现有一个字典列表,想根据字典的某个或者某几个字段来排序这个列表
解决方案: 使用opeator模块中的itemgetter函数
示例1:

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
# 根据'fname'字段排序
rows_by_fname = sorted(rows, key=itemgetter('fname'))
print("result1:\n", rows_by_fname)
# 根据'uid'字段排序
rows_by_uid = sorted(rows, key=itemgetter('uid'))
print("result2:\n", rows_by_uid)
# 根据'lname'和'fname'排序
rows_by_lfname = sorted(rows, key=itemgetter('lname','fname'))
print("result3:\n", rows_by_lfname)

输出:

result1:
[{‘fname’: ‘Big’, ‘lname’: ‘Jones’, ‘uid’: 1004},
{‘fname’: ‘Brian’, ‘lname’: ‘Jones’, ‘uid’: 1003},
{‘fname’: ‘David’, ‘lname’: ‘Beazley’, ‘uid’: 1002},
{‘fname’: ‘John’, ‘lname’: ‘Cleese’, ‘uid’: 1001}]
result2:
[{‘fname’: ‘John’, ‘lname’: ‘Cleese’, ‘uid’: 1001},
{‘fname’: ‘David’, ‘lname’: ‘Beazley’, ‘uid’: 1002},
{‘fname’: ‘Brian’, ‘lname’: ‘Jones’, ‘uid’: 1003},
{‘fname’: ‘Big’, ‘lname’: ‘Jones’, ‘uid’: 1004}]
result3:
[{‘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()有时候可以用lambda表达式代替。但使用itemgetter()通常比较快。
示例2:

rows_by_fname = sorted(rows, key= lambda x: x['fname'])
rows_by_lfname = sorted(rows, key= lambda x: (x['lname'], x['fnae']))

itemgetter()也可用于min()和max()函数。
示例3:

min_by_uid = min(rows, key= itemgetter('uid'))
max_by_uid = max(rows, key= itemgetter('uid'))
print(min_by_uid)
print(max_by_uid)

输出:

{‘fname’: ‘John’, ‘lname’: ‘Cleese’, ‘uid’: 1001}
{‘fname’: ‘Big’, ‘lname’: ‘Jones’, ‘uid’: 1004}

14. 使用operator.attrgetter()函数获取属性值,并根据属性值排序对象

问题描述: 你想排序类型相同的对象,但是他们不支持原生的比较操作。
解决方案: (1)使用lambda函数;(2)利用operator.attrgetter()。
示例1:

class User:
    def __init__(self, user_id):
        self.user_id = user_id

    def __repr__(self):
        return 'User({})'.format(self.user_id)
        
users = [User(23), User(3), User(99)]
print(users)
# 根据列表中每个对象的user_id属性进行排序
print(sorted(users, key=lambda u: u.user_id))

输出:

[User(23), User(3), User(99)]
[User(3), User(23), User(99)]


示例2:
利用operator.attrgetter()

from operator import attrgetter
print(sorted(users, key=attrgetter('user_id')))

输出:

[User(3), User(23), User(99)]


同样可以用于min()或max()函数:

print(min(users, key=attrgetter('user_id')))
print(max(users, key=attrgetter('user_id')))

输出:

User(3)
User(99)

15. itertools.groupby()函数对连续相同的数据进行分组

问题描述: 现有一个字典或者实例的序列,你想根据某个字段比如date来分组迭代。
解决方案: 利用itertools.groupby()函数。
示例:

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'},
    {'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'},
]

# 首先根据'date'字段排序
rows.sort(key=itemgetter('date'))
# 然后调用groupby()函数
for date, items in groupby(rows, key=itemgetter('date')):
    print(date)
    for i in items:
        print(' ', i)

输出:

07/01/2012
{‘address’: ‘5412 N CLARK’, ‘date’: ‘07/01/2012’}
{‘address’: ‘4801 N BROADWAY’, ‘date’: ‘07/01/2012’}
07/02/2012
{‘address’: ‘5800 E 58TH’, ‘date’: ‘07/02/2012’}
{‘address’: ‘5645 N RAVENSWOOD’, ‘date’: ‘07/02/2012’}
{‘address’: ‘1060 W ADDISON’, ‘date’: ‘07/02/2012’}
07/03/2012
{‘address’: ‘2122 N CLARK’, ‘date’: ‘07/03/2012’}
07/04/2012
{‘address’: ‘5148 N CLARK’, ‘date’: ‘07/04/2012’}
{‘address’: ‘1039 W GRANVILLE’, ‘date’: ‘07/04/2012’}

groupby() 函数扫描整个序列并且查找连续相同值(或者根据指定 key 函数返回值相同)的元素序列。在每次迭代的时候,它会返回一个值和一个迭代器对象,这个迭代器对象可以生成元素值全部等于上面那个值的组中所有对象。
一个非常重要的准备步骤是要根据指定的字段将数据排序。因为 groupby() 仅仅检查连续的元素,如果事先并没有排序完成的话,分组函数将得不到想要的结果。


方法二:利用多值字典也可以实现分组
示例:

# defaultdict使用
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)

输出:

{‘address’: ‘5412 N CLARK’, ‘date’: ‘07/01/2012’}
{‘address’: ‘4801 N BROADWAY’, ‘date’: ‘07/01/2012’}

16. 过滤出序列中需要的元素

问题描述: 你有一个数据序列,想利用一些规则从中提取出需要的值或者是缩短序列
方法一:列表推导式

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

输出:

[1, 4, 10, 2, 3

方法二:生成器表达式
列表表达式的一个缺陷是是当输入非常大时,会产生一个非常大的结果集,占用大量内存。而生成器表达式可以避免这个问题。

pos = (n for n in mylist if n > 0)
print(pos)
for x in pos:
    print(x, end=',')

输出:

<generator object at 0x7f5f005d5b50>
1,4,10,2,3,

方法三:filter()函数

def is_int(val):
    try:
        x = int(val)
        return True
    except ValueError:
        return False
ivals = list(filter(is_int, values))
print(ivals)

输出:

[‘1’, ‘2’, ‘-3’, ‘4’, ‘5’]

注意filter()函数返回的是一个迭代器。
方法四:itertools.compress()
它以一个 iterable对象和一个相对应的 Boolean 选择器序列作为输入参数。然后输出 iterable 对象中对应选择器为 True 的元素。

from itertools import compress
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]
more5 = [n > 5 for n in counts]
print(list(compress(addresses, more5)))

输出:

[False, False, True, False, False, True, True, False]
[‘5800 E 58TH’, ‘1060 W ADDISON’, ‘4801 N BROADWAY’]

17. 从字典中提取子集

问题描述: 你想构造一个字典,它是另一个字典的子集
解决方案: 字典表达式
示例:

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}

18. collections.namedtuple()命名元组

问题描述: 如何通过名称而不是下标来访问元组,以提高可读性?
解决方案: 使用collections.namedtuple()函数,将类型名和所需要的字段传递给它,然后会返回一个类,可以用这个类初始化一个命名元组
示例:

subscriber = namedtuple('Subscriber', ['addr', 'joined']) #subscriber为返回的命名元组类
sub = subscriber('jonesy@example.com', '2012-10-19') # 初始化一个subscriber元组
print(sub)
print(sub.addr, sub.joined)

# 可以像操作元组那样使用subscribe类
print(len(sub))
addr, joined = sub
print(addr, joined)

输出:

Subscriber(addr=‘jonesy@example.com’, joined=‘2012-10-19’)
jonesy@example.com 2012-10-19
2
jonesy@example.com 2012-10-19

19. 转换并同时计算数据

问题描述: 你需要在数据序列上执行聚合函数(比如sum(), min(), max()),但是首先你需要先转换或者过滤数据
解决方案: 使用一个生成器表达式参数。
示例1:

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

输出:

55


示例2:
检查某文件夹下是否包含.py文件

import os
files = os.listdir('dirname') #os.listdir()方法得到该文件夹下的所有文件或文件夹
if any(name.endswith('.py') for name in files):
    print('There be python!')
else:
    print('Sorry, no python.')

示例3:
将一个list中的元素连接成字符串

s = ('ACME', 50, 123.45)
print('-'.join(str(x) for x in s))

输出:

ACME-50-123.45


示例4:

portfolio = [
    {'name':'GOOG', 'shares': 50},
    {'name':'YHOO', 'shares': 75},
    {'name':'AOL', 'shares': 20},
    {'name':'SCOX', 'shares': 65}
]
min_shares = min(s['shares'] for s in portfolio)
print(min_shares)

输出:

20

20. collections.ChainMap()合并多个字典或映射

问题描述: 将多个字典或者映射从逻辑上合并为一个单一的映射后执行操作
解决方案: 使用collections.ChainMap()
示例1:

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

输出:

1
2
3

如果出现重复键,那么第一次出现的映射值会被返回。因此,例子程序中的 c[‘z’]总是会返回字典 a 中对应的值,而不是 b 中对应的值。


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

c['z'] = 10
c['w'] = 40
del c['x']
print(a)

输出:

{‘z’: 10, ‘w’: 40}


update()方法也可以将两个字典合并,但是它需要你创建一个完全不同的字典对象(或者是破坏现有字典结构)。

a = {'x': 1, 'z': 3 }
b = {'y': 2, 'z': 4 }
merged = dict(b)
merged.update(a)
print(merged)

输出:

{‘y’: 2, ‘z’: 3, ‘x’: 1}

参考文献:
https://github.com/yidao620c/python3-cookbook

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值