Python Cookbook第三版学习笔记【第一章】

第一章:数据结构和算法

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

  • 任何可迭代对象都可以分解为单独的变量,如:
p = (4, 5)
x, y = p
  • 如果要丢弃某些特定的值,则使用用不到的变量代表该值,如:
person = [1, "jimmy", 22, "china"]
_, name, age, _ = person 

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

  • 使用"*表达式",如:
>>> person = [1, "jimmy", 22, "china"]
>>> id, *info = person
>>> id
1
>>> info
['jimmy', 22, 'china']
>>>
  • 在不确定可迭代对象的长度的时候,可以使用这种方法避免等号左右两端长度不等

1.3 保存最后N个元素

  • 在一次迭代过程中只保留最后的几条处理记录
  • 使用collections.deque, 可以定义一个固定长度的队列,当有新元素加入时,会自动移除最老的记录。
from collections import deque
q = deque(maxlen=3)
  • 如果不指定maxlen,将会得到一个无界限的队列,可以在两端添加和弹出元素
q.appendleft(1)
q.popleft()
q.append(1)
q.pop()
  • 从队列两端添加和弹出元素时间复杂度都是O(1)

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

  • heapq.nlargest(n, iterable, key=None) / heapq.nsmallest(n, iterable, key=None)
  • 以上两个函数分别找到一个可迭代对象中的n个最大、最小值
  • key接收一个函数,可以定义比较大小的规则
  • 如果N远小于集合中元素的总数,可以用堆:
# nums 为一个集合
import heapq
heap = list(nums)
heapq.heapify(heap) # heap中的元素将会以升序排序
heap.pop() # 弹出最小的元素
  • 如果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]

    def empty(self):
        return len(self._queue) == 0
  • 上述代码中:
    • push 方法往self._queue中添加元素,添加的元素为元组:(-priority, self._index, item),优先级由这个元组本身定义,即:第一规则为-priority(python中的优先队列总是递增排列的,取相反数可以起到逆序,即priority最大的,处在队列头部。如果-priority相等,则按self._index排列,这个值是不断递增的,因此先添加的元素有更高的优先级
    • pop 方法只取元素中的最后一项,即item
  • 再定义一个Item类
class Item:
    def __init__(self, name):
        self.name = name
  • 使用这个优先队列
a = Item('apple')
b = Item('banana')
c = Item('carrot')
o = Item('orange')

q = PriorityQueue()
q.push(a, 3) # apple
q.push(b, 4) # banana
q.push(c, 2) # carrot
q.push(o, 2) # orange

while not q.empty():
    print(q.pop().name)
  • 结果:
banana
apple
carrot
orange
  • 优先级定义为4的banana率先出列,为3的apple其次,优先级相同的carrot和orange则以先入列的carrot先出列
  • 如果不引入_index,当priority相等时,因Item类本身没有实现比较大小的特殊方法,将会报如下错误
TypeError: '<' not supported between instances of 'Item' and 'Item'

1.6 在字典中将键映射到多个值上[multidict]

  • 使得一个key对应多个value,就是希望当不断(在循环中)对同一个key添加值的时候,可以用一个列表保存这些值(如果希望对value去重,则使用set)
  • 使用collections.defaultdict
from collections import defaultdict
d1 = defaultdict(list)
d1['a'].append(1) # 可直接添加值,不需要手动初始化

d2 = defaultdict(set) # 集合
d2['a'].add(1)

counter = defaultdict(int) # value被初始化为0, 可用作计数
counter['a'] += 1

1.7 使字典保持有序

  • 使用collections.OrderedDict, 当迭代字典时,会严格按照添加元素的顺序进行
  • 适用场景:若要对字典序列化或编码,OrderedDict可以使得序列化后的对象保持顺序
  • OrderedDict内部维护了双向链表,新添加的键被放在链表末尾
  • 由于此双向链表,OrderedDict的大小是普通字典的2倍多
  • 自Python3.6起,普通的字典dict已经是有序的

1.8 与字典有关的计算问题

  • 如果希望对字典中的数据作数值计算(求最大值、最小值、排序等等)
  • 如果直接使用字典进行计算,则会直接作用于key上:
>>> d = {"a": 1, "b": 3, "c": 2}
>>> d
{'a': 1, 'b': 3, 'c': 2}
>>> sorted(d)
['a', 'b', 'c']
>>> min(d)
'a'
  • 如果对value进行计算,则得不到结果和key的对应关系:
>>> sorted(d.values())
[1, 2, 3]
>>>
  • 解决方法:使用zip(), 反转key,value对,并保持key和value的对应关系
>>> sorted(zip(d.values(), d.keys()))
[(1, 'a'), (2, 'c'), (3, 'b')]
  • 如果有相同的value,则会比较它们的key作为大小的判据

1.9 在两个字典中寻找相同点

  • 想要找到两个字典的key或者value的交集、补集,使用dict.keys()和dict.items()
  • 如下的两个字典:
a = {
    'x': 1,
    'y': 2,
    'z': 3
}

b = {
    'w': 1,
    'y': 4,
    'z': 3
}

  • 使用"&" “-” 运算符,如下:
print(a.keys() & b.keys()) 字典a和b公有的key
print(a.items() - b.items()) 字典a中有而在b中没有的item
  • 结果:
{'z', 'y'}
{('y', 2), ('x', 1)}

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

  • 如果:
# a是一个序列
res = list(set(a))
  • set是无序的,因此无法保障元素间顺序不变的要求
  • 因此使用生成器+set来去重并保持顺序
def remove_dup_val(items):
    s = set()
    for item in items:
        if item not in s:
            yield item
        s.add(item)
  • 如上生成器,每次调用返回一个不重复的元素
a = [1, 2, 2, 4, 6, 7, 1, 2, 6, 5]
res = list(remove_dup_val(a))
  • 结果:
[1, 2, 4, 6, 7, 5]
  • 仅当要去重的序列中的元素都是可哈希的(在生命周期中不可变的)元素才可以这样做
  • 如果不是可哈希的,如实例、字典,可取用其中的属性(或value)来作为去重的判据,那么上面的代码改成
def remove_dup_val(items, key=None):
    s = set()
    for item in items:
        if key is not None:
            i = key(item)
            if i not in s:
                yield item
            s.add(i)
  • 这时候,加入set中的就不是元素本身,而是在key中指定的元素的一个属性(value):
a = [{'a': 1, 'b': 2},
     {'a': 0, 'b': 3},
     {'a': 1, 'b': 3},
     {'a': 4, 'b': 1}]

# 以字典中的key b作为去重的条件
res = list(remove_dup_val(a, key=lambda x: x['b']))
  • 结果:
[{'a': 1, 'b': 2}, {'a': 0, 'b': 3}, {'a': 4, 'b': 1}]
  • 也可以使用多个key作为条件, 放在元组中即可:定义key为lamda x: (x[‘a’], x[‘b’])

1.11 对切片命名

data = 'DATA_MATRIX_20200514.mat'
DATA_CREATE_DATE = slice(12, 20)
data[DATA_CREATE_DATE]
  • 通过以上代码可以从文件名中取得日期,代码的意图更加易懂
  • 如果有一个slice实例s和一个序列arr, s.indices(len(arr)) 会返回一个(start, stop, step)元组,这样,在遍历这个序列的时候,就不会超出边界引发IndexError

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

import random
from collections import Counter

nums = [random.randrange(20) for _ in range(100)]
num_count = Counter(nums)

# 统计出现最多的3个数
res = num_count.most_common(3)
  • 结果:5,1,7 分别出现了10, 8, 8次
[(5, 10), (1, 8), (7, 8)]
  • 可以手动的增加计数:
num_count[some_number] += 1
  • 也可以添加一个新序列更新计数:
num_count.update(new_nums)
  • Counter实例也可以应用于数学运算:
# 结果集相减
num_count1 - num_count2
# 结果集叠加
num_count1 + num_count2

1.13 通过公共键对字典列表进行排序

persons = [
    {
        'name': 'mary',
        'age': 14
    },
    {
        'name': 'john',
        'age': 16
    },
    {
        'name': 'tom',
        'age': 11
    }
]
  • 若想对age年龄排序,可以:
sorted_persons = sorted(persons, key=lambda x:x['age'])
  • key接受一个callable参数,传入用于排序的字段
  • operator.itemgetter()可以产生一个callable,完成和上面的匿名函数lambda一样的功能:
res = sorted(persons, key=itemgetter('age'))
# res = sorted(persons, key=itemgetter('age', 'name')) 对多个字段进行排序
  • itemgetter 效率比lambda通常更高,也可用于类似min, max的函数:
eldest = max(persons, key=itemgetter('age'))

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

  • 对于实例对象,和字典(1.13节)类似,用operator.attrgetter()取出属性对对象进行排序,如:
# class User
from operator import attrgetter
sorted(users, key=attrgetter('user_id', 'user_name'))

1.15 根据字段将记录分组

  • 可以先对记录进行排序,然后使用itertools.groupby() 函数进行分组
  • 例如,想对下面的人按年龄分组:
import itertools

persons = [
    {
        'name': 'mary',
        'age': 14
    },
    {
        'name': 'john',
        'age': 16
    },
    {
        'name': 'tom',
        'age': 11
    },
    {
        'name': 'nancy',
        'age': 14
    },
    {
        'name': 'peter',
        'age': 16
    }
]
  • 然后对年龄先排序,后分组:
persons.sort(key=itemgetter('age'))
res = groupby(persons, key=itemgetter('age'))
  • groupby的结果是一个迭代器,每一项是一个元组,元组中第一个元素是分组字段的值,此例中是年龄age,第二个元素也是一个迭代器,即分到这组中的记录:
for value, items in res:
    print("People that age {}".format(value))
    for item in items:
        print(item['name'])
  • 结果:
People that age 11
tom
People that age 14
mary
nancy
People that age 16
john
peter
  • 也可以用1.6节描述的collections.defaultdict,只需将defaultdict实例的value初始化为list,遍历persons,将年龄作为defaultdict实例的key,也可实现分组,这样做效率更高,但是要额外引入一个defaultdict实例,使用了额外的内存。

1.16 筛选序列中的元素

  • 筛选序列中的元素,对于一般的问题可以使用列表推导式:
[x for x in some_array if condition]
  • 考虑到内存因素,也可以使用生成器表达式
  • 如果筛选条件codition比较复杂,可以使用filter()函数:
def condition(x):
	```your logic here```
	pass
filtered_items = list(filter(condition, some_array))
  • 如果要对不满足条件的值进行替换,例如将序列中的负数替换为0:
[x if x >= 0 else 0 for x in some_array]
  • 假设有两个关联的序列,如下面的persons和grades,grades中的每一项代表对应的person的成绩:
persons = [
    {
        'name': 'mary',
        'age': 14
    },
    {
        'name': 'john',
        'age': 16
    },
    {
        'name': 'tom',
        'age': 11
    },
    {
        'name': 'nancy',
        'age': 14
    },
    {
        'name': 'peter',
        'age': 16
    }
]

grades = [90, 65, 92, 83, 79]
  • 想要筛选出成绩不低于90的person,使用itertools.compress():
from itertools import compress
res = list(compress(persons, [grade >= 90 for grade in grades]))
  • compress的第二个参数是布尔值序列,可以筛选出第一个输入值persons对应的值为True的元素。

1.17 从字典中提取子集

  • 有以下字典,代表了商品的价格:
prices = {
    'budweiser': 7,
    'corona': 8,
    'guinness': 6,
    'carlsberg': 11,
    'heineKen': 5
}
  • 以下是偏好的品牌:
preferred_brands = ['budweiser', 'heineKen']
  • 使用字典推导式可以筛选出属于偏好品牌的商品:
res = {k: v for k, v in prices.items() if k in preferred_brands}
  • 也可以写成:
res = {k:prices[k] for k in prices.keys() & preferred_brands}
  • 但是第一种方式性能更高

1.18 将名称映射到序列的元素中

  • 访问元组中的元素只能通过索引值,代码易读性不高,使用collections.namedtuple可以给字段命名
  • namedtuple接收两个必选参数,一个是元组的名字,一个是元组的字段:
  • 它返回的是元组的子类,包含了命名的字段:
Person = namedtuple('Person', ['name', 'age', 'nationality'])
p1 = Person('jimmy', 21, 'Japan')
p2 = Person('tom', 25, 'United States')
  • 然后,就可以用如下方式访问元组的字段:
print(p1.name)
print(p1.nationality)
  • 命名元组支持元组的一切操作
  • 不使用索引访问元组不仅提高了代码的易读性,也使得当访问的元组的结构变更时,代码不至于崩溃
  • namedtuple可以作为字典的替代,它比后者更节省空间。但因为元组是不可变的,所以如果要修改namedtuple中的值,要调用它的_replace方法,该方法创建一个全新的命名元组进行替换:
p1 = p1._replace(age=30)
print(p1)
  • 可以这样使用_replace方法:
# 1. 定义一个“原型”元组:
proto_person = Person('', 0, '')

# 2. 根据这个“原型”元组创建“真正”实例
real_person = proto_person._replace(name='jimmy', age=21, nationality='Japan')

# 也可以用这个方法将一个字典转成命名元祖:
person = {
	"name": "jimmy",
	"age": 21,
	"nationality": "Japan"
}

real_person = proto_person._replace(**person)

1.19 同时对数据做转换和换算

  • 想对一个序列先做转换操作,再对其调用类似min, max, sum的函数(类似map/reduce的操作)
  • 比如,希望对nums的每一项求平方,并求和,可以这样做:
sum(x**2 for x in nums)
  • 实际上是给sum传入了一个生成器表达式,语法上甚至略去了生成器表达式的括号
  • 相比于使用列表推导式:
sum([x**2 for x in nums])
  • 后者需要分配临时空间,如果nums十分巨大,这种做法将不够高效

1.20 将多个映射合并为一个映射

  • 可以用collections.ChainMap() 将字典合并,这样就可以对多个字典进行统一的查询
a = {
    'x': 1,
    'y': 2
}

b = {
    'x': 4,
    'z': 5
}

c = ChainMap(a, b)
  • print(list(c.items())), 得到:
[('x', 1), ('z', 5), ('y', 2)]
  • 如上面的例子,如果a和b中有重复的key “x”,合并后采用的是a也即第一个字典中的key,如果尝试修改x的值,如:
c['x'] = 99
print(a)
print(b)
  • 结果:
{'x': 99, 'y': 2}
{'x': 4, 'z': 5}
  • 只会修改a的key x的值
  • 如果给c多添加一个key:
c['w'] = 100
print('a is:', a)
print('b is:', b)
  • 结果:
a is: {'x': 1, 'y': 2, 'w': 100}
b is: {'x': 4, 'z': 5}
  • 这个key只会加到a上
  • ChainMap还可以发挥作用域的功能,使得不同作用域拥有相同的key,但各自的值不同:
values = ChainMap()
values['x'] = 1
values = values.new_child()
values['x'] = 2
values = values.new_child()
values['x'] = 3

print(values['x'])
values = values.parents
print(values['x'])
values = values.parents
print(values['x'])
  • 结果:
3
2
1
  • 使用字典的update方法也可以合并两个字典
a.update(b)
  • 这样, a 中并入了b中的字段,但是对a进行改动时,不会影响到b,而ChainMap中使用的是原始的字典,对合并后的字典的改动直接作用于合并前的字典上。

小结:本章使用的类、函数

  • 类:
collections.deque # 一个可以固定长度的队列,超过长度时,队首元素自动弹出
collections.defaultdict # 具有默认值的字典,使得一个键可以映射到多个值上(列表、集合)
collections.OrderedDict # 有序的字典,内部维护了双向链表以保持顺序
slice # 切片,使得切片可以被命名
operator.itemgetter # 取得字典中的key对应的值,本章中用于排序函数(sorted, sort)中的key参数定义用于排序的字段,代替了lambda命名函数的作用
operator.attrgetter # 与itemgetter
itertools.groupby # 对序列进行分组,分组前必须先对序列排序
collections.ChainMap # 合并多个字典(映射)
  • 函数:
heapq.heapify / heapq.heappush / heapq.heappop
'''
堆相关的操作:
heapify 可以将一个列表以inplace的方式转换成堆,
heappush可以向堆中压入一个元素,
heappop则弹出一个元素
''' 
collections.namedtuple # 定义一个命名元组的模板,命名元组属于元组的子类,具备元组的一切特性和操作
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值