0%

Python-数据结构与算法

常用的一些必备模块。如:

  • collections:【标准库】Python 增强数据类型,常用如 deque 双端队列,OrderedDict 有序字典,Counter 计数器
  • operator:【标准库】标准运算符替代函数
  • itertools:【标准库】一系列迭代功能的函数。返回 Iteratorfor 迭代循环时才计算。
  • functools:【标准库】高阶函数
  • contextlib:将任意对象变为上下文对象,并支持 with 语句。
  • queue:【标准库】一个同步的队列类
  • celery:一个异步任务队列/作业队列,基于分布式消息传递。
  • rq:简单的 Python 作业队列。
  • heapq:【标准库】:堆队列算法,求前 n 个最大/最小的数字
  • array:高效的数值数组
  • dataclasses:数据类,python3.7 推出的,面向数据的思维,节省写 __init__() 等实例方法
  • enum:【标准库】枚举类

概述

collections

collections:【标准库】Python 增强数据类型,常用如 deque 双端队列,OrderedDict 有序字典,Counter 计数器

  • defaultdict:带有默认值的字典,适合处理字典键值对
  • deque:双端队列,用于快速插入和删除操作

deque 双端队列

  • 可从头或尾部高效地添加或删除元素,方便实现队列与栈
  • 队列:常用来传递任务和接收结果
    • 任务的描述数据量要尽可能小,如发送一个处理日志文件的任务,不要发送几百 MB 的日志文件,而是发送文件的完整路径。
1
2
3
4
5
6
from collections import deque
q = deque([1, 2, 3])
q.append(99)
q.appendleft(0)
q.pop()
q.popleft()

OrderedDIct 有序字典

  • 注意,OrderedDict 的 Key 会按照插入的顺序排列,不是 Key 本身排序:
    • OrderedDict 可以实现一个 FIFO(先进先出)的 dict,当容量超出限制时,先删除最早添加的 Key:
1
2
3
4
5
6
7
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 计数器

  • 统计元数个数
    • 统计字符串中字符出现的次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from collections import Counter
c=Counter("helloworld")
c
Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, 'w': 1, 'r': 1, 'd': 1})
c.most_common(2) # 出现次数前 2 多的

# 可用来判断两个列表元素是否一样
one = [33, 22, 11, 44, 55]
two = [22, 11, 44, 55, 33]

print("two lists are equal.", Counter(one) == Counter(two))


 from collections import Counter
 ​
 items = ['apple', 'banana', 'apple', 'orange', 'banana', 'banana']
 count = Counter(items)
 print(count) # 输出: Counter({'banana': 3, 'apple': 2, 'orange': 1})

namedtuple

namedtuple:命名元组,通过属性访问元

1
2
3
4
5
6
7
8
9
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
print(p.x)
print(p.y)
1
2
# namedtuple('名称', [属性 list]):
Circle = namedtuple('Circle', ['x', 'y', 'r']) #用坐标和半径表示一个圆

defaultdict:默认值字典

  • 在字典中如果使用的 key 不存在,就会抛出 KeyError 错误,假如我们希望在 key 不存在的时候,返回自定义的值时可使用 defaultdict。
  • 除了在 Key 不存在时返回默认值,defaultdict 的其他行为跟 dict 是完全一样的。
1
2
3
4
5
6
7
from collections import defaultdict
dd = defaultdict(lambda: 'N/A')
dd['key1'] = 'abc'
dd['key1'] # key1 存在
'abc'
d['key2'] # key2 不存在,返回默认值
'N/A'

operator

operator:【标准库】标准运算符替代函数

  • Python 内置的操作符函数接口,定义了一些算术和比较内置操作的函数,基于 C,执行速度快。
1
2
3
4
5
6
7
8
9
10
11
12
13
#内置函数
operator.lt(a, b) # true:a<b :从第一个数字或字母(ASCII)比大小
operator.le(a, b) # true: a<=b
operator.eq(a, b) # true: a==b
operator.ne(a, b) # true: a!=b
operator.ge(a, b) # true: a>b
operator.gt(a, b) # true: a>=b
operator.__lt__(a, b)
operator.__le__(a, b)
operator.__eq__(a, b)
operator.__ne__(a, b)
operator.__ge__(a, b)
operator.__gt__(a, b)

itertools

itertools:【标准库】一系列迭代功能的函数。返回 Iteratorfor 迭代循环时才计算。

  • 高效迭代器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from itertools import combinations
list1=['chen','zhang','li']
for i in combinations(list1,2):
print(i)



# 无限迭代器
itertools.count()

# 无限重复器
a=itertools.cycle("chen")
for i in a:
print(i) # 会一直输出 'c','h','e','n'
# repeat:单个元素,可设置重复次数
a=itertools.repeat('Abc',3) #Abc 看作一块

a=itertools.count(1)
ns=itertools.takewhile(lambda x:x<=10,a) # 根据条件判断来截取出一个有限的序列
print(list(ns))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 元素拼接:嵌套列表转列表,将一系列迭代对象连接起来,形成一个更大的迭代器。
list(itertools.chain(['my','name','is'],['??']))
['my', 'name', 'is', '??']


# 将迭代器中相邻的重复元素挑出来放在一起
# 函数作用于两个元素返回的值相等,即认为它们是一组的,返回值作为 key
for key,group in itertools.groupby('AAAAbBbCCC',lambda c:c.upper()):
print(key,list(group))

A ['A', 'A', 'A', 'A']
B ['b', 'B', 'b']
C ['C', 'C', 'C']

>>> x = itertools.groupby(range(10), lambda x: x < 5 or x > 8)
>>> for condition, numbers in x:
... print(condition, list(numbers))
True [0, 1, 2, 3, 4]
False [5, 6, 7, 8]
True [9]

accumulate

accumulate(iterable[, func, *, initial=None]):列表的累积汇总

1
2
3
list(itertools.accumulate([1,2,3,4,5],lambda x,y:x*y))
# [1, 2, 6, 24, 120]
list(itertools.accumulate([1,2,3,4,5])) # [1, 3, 6, 10, 15]

排列组合

1
2
3
4
5
6
7
# 求列表或生成器中指定数目的元素不重复的所有组合
from itertools import combinations,permutations
list(combinations([1,2,3,4,5],2)) # 组合
list(permutations([1,2,3,4,5],2)) # 排列

# 允许重复元素的组合
x = itertools.combinations_with_replacement('ABC', 2)

product

1
2
3
4
5
6
7
8
def find_twelve(num_list1, num_list2, num_list3):
"""从 3 个数字列表中,寻找是否存在和为 12 的 3 个数
"""
for num1 in num_list1:
for num2 in num_list2:
for num3 in num_list3:
if num1 + num2 + num3 == 12:
return num1, num2, num3
1
2
3
4
5
6
7
# 扁平化多层嵌套
from itertools import product

def find_twelve_v2(num_list1, num_list2, num_list3):
for num1, num2, num3 in product(num_list1, num_list2, num_list3):
if num1 + num2 + num3 == 12:
return num1, num2, num3

chain

  • 连接多个可迭代对象,便于合并数据
1
2
3
4
5
6
 from itertools import chain  
 ​
 a = [1, 2, 3]
 b = [4, 5]
 result = list(chain(a, b))
 print(result) # 输出:[1, 2, 3, 4, 5]
  • product:生成笛卡尔积
  • combinations:生成元素组合
  • permutations:生成排列

functools

functools:【标准库】高阶函数,函数操作的利器

  • 高效处理函数操作,简化代码结构,常用于高阶函数或装饰器
1
2
3
4
5
6
7
8
9
10
11
cached_property
cmp_to_key
lru_cache
partial
partialmethod
reduce
singledispatch
singledispatchmethod
total_ordering
update_wrapper
wraps
  • partial:创建一个带默认参数的函数。
  • lru_cache:缓存函数结果
  • reduce:执行累积计算

lru_cache

  • 缓存函数结果,在后续调用相同的参数的该函数时,不会再执行,而是直接用结果。
    • 对于计算量大 或 使用相同参数频繁调用的函数特别有用
  • 缓存机制,加快数据的速度
1
2
@functools.lru_cache(maxsize=None, # 最多可缓存多少个此函数的调用结果,None 为无限制,常设为 2 的幂
typed=False) # 若为 True,不同参数类型的调用将分别缓存
1
2
3
4
5
6
7
8
9
10
from functools import lru_cache

@lru_cache(None)
def add(x, y):
print("calculating: %s + %s" % (x, y))
return x + y

print(add(1, 2))
print(add(1, 2))
print(add(2, 3))
1
2
3
4
5
6
7
8
9
10
11
12
# 速度测试
import timeit
from functools import lru_cache

@lru_cache(None)
def fib(n):
if n < 2:
return n
return fib(n - 2) + fib(n - 1)

print(timeit.timeit(lambda :fib(500), number=1))
# output: 0.0004921059880871326

contextlib

contextlib:将任意对象变为上下文对象,并支持 with 语句。

  • 即可使用 with,不用担心资源没 close
  • 易用的上下文管理器工具,通过生成器实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from contextlib import contextmanager
class Query(object):
def __init__(self, name):
self.name = name
def query(self):
print('Query info about %s...' % self.name)

@contextmanager
def create_query(name):
print('Begin')
q = Query(name)
yield q
print('End')

# 这样就可以使用
with create_query('Bob') as q:
q.query()
1
2
3
4
import contextlib
with contextlib.closing(urllib.urlopen("http://www.python.org/")) as front_page:
for line in front_page:
print line

queue

queue:【标准库】一个同步的队列类

  • 常用于让多线程编程更安全

celery

celery:一个异步任务队列/作业队列,基于分布式消息传递。

  • 简单、灵活、可靠的分布式系统,消息队列工具,用于处理实时数据及任务调度

rq

rq:简单的 Python 作业队列。

heapq

heapq:【标准库】:堆队列算法,求前 n 个最大/最小的数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import heapq
import random
data=random.shuffle(list(range(10)))
heap_a=[]
for i in data:
heapq.heappush(heap_a,i)
heapq.heappush(heap_a,2.5)
heap.heappop(heap_a)
heapq.heapify(list(range(10))) #建堆
heapq.heapreplace(heap_a,6) #弹出最小,插入新元素 6


heapd.nlargest(3,heap_a) # 返回最大的 3 个元素
heap.nsmallest(3,heap_a) # 返回最小的 3 个元素

grades = [110, 25, 38, 49, 20, 95, 33, 87, 80, 90]
print(heapq.nlargest(3, grades))
print(heapq.nsmallest(4, grades))

# output
[110, 95, 90]
[20, 25, 33, 38]

array

array:高效的数值数组

dataclasses

#最佳实践

dataclasses:数据类,一个带有默认值的可变的 numedtuple。python3.7 推出的,面向数据的思维,节省写 __init__() 等实例方法

  • 推荐使用它而不是 namedtuple
  • 相比于普通 class:
    • dataclass 通常不包含私有属性,数据可直接访问
    • repr 通常有固定格式,会打印出类型名、属性名及对应的值
    • 具有 __eq____hash__ 魔法方法
    • 模式单一固定的构造方法。使用类型注解语法,生成一个实现了 __init____repr____cmp__ 等方法的 dataclass
  • 相比于 enum:
    • Enum 主要是将不可变的名称/值对包含在可迭代对象中
    • dataclass:具有默认值的可变命名元组
  • 继承:
    • dataclass 装饰器会检查当前 class 的所有基类,如果发现一个 dataclass,就会把它的字段按顺序添加进当前的 class,随后再处理当前 class 的 field。所有生成的方法也将按照这一过程处理,因此如果子类中的 field 与基类同名,那么子类将会无条件覆盖基类。子类将会根据所有的 field 重新生成一个构造函数,并在其中初始化基类。
  • dataclass 通常情况下是 unhashable 的,因为默认生成的 __hash__None,所以不能用来做字典的 key,如果有这种需求,那么应该指定你的数据类为 frozen dataclass
  • 小心当你定义了和 dataclass 生成的同名方法时会引发的问题
  • 当使用可变类型(如 list)时,应该考虑使用 fielddefault_factory
  • 数据类的属性都是公开的,如果你有属性只需要初始化时使用而不需要在其他时候被访问,请使用 dataclasses.InitVar
  • 限制 hash:限制为不可变字段`field(hash=False)
  • 显示限制的字段:field(repr=Fase)
  • 比较限制:(在比较时排除这个字段):field(compare=False)
  • list_1.remove(Test(name=’chen’)),当 Test 是 dataclasses 时,只要它的属性是一致的,就会认为是同一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from dataclasses import dataclass,field,fields
from typing import Any
from typing import List

@dataclass
class Person:
name:str='chen'
age:int=field(default=18,metadata={'msg':'always 18'})
class_:str
any_type: Any = 42

def print_age(self):
print(self.age)
return None

def __str__(self):
# 定义 print 的输出时的格式
return f'{self.name}:{self.age}'

@dataclass(
init=True, # 是否自动生成 __init__,如果已定义同名方法则忽略该值
repr=True, # 是否自动生成 __repr__,自动生成的打印格式为 class_name(attr1:value1, attr2:value2,..)
eq=True, # 是否生成 __eq__,按属性在类内定义时的顺序逐个比较,全部的值相同时才会返回True
order, # 自动生成 __lt__,__le__,__gt__,__ge__,如果order指定为True而eq指定为False,将引发`ValueError`;如果已经定义同名函数,将引发`TypeError`
unsafehash=False,
frozen=False, # 设为True时对field赋值将会引发错误,对象将是不可变的,如果已经定义了`__setattr__`和`__delattr__`将会引发`TypeError`
)
class Persons:
many_person: List[Person]

@dataclass
class Student(Person):
stu_id: str

chen=Person('chen','18','math')
print(chen)
print(chen.name)
print(chen.print_age())
# fields:查看属性的附加信息
print(fields(Person))

print(chen.name)
print(chen.age)


# 生成的`__init__`方法在返回之前调用`__post_init__`
@dataclass
class FloatNumber:
val: float = 0.0

def __post_init__(self):
self.decimal, self.integer = math.modf(self.val)

__post_init__

  • datacalss会先根据你定义的字段,自动生成一个标准的 __init__ 方法,这个方法只做一件事:把你传入的参数赋值给 self。然后立即调用 __post_init__
  • 建议将所有复杂的初始化逻辑,写到 __post_init__
    • __init__:由dataclass 自动生成,进行数据绑定(将创建实例时传入的数据正确地绑定到实例的属性上)
      • dataclass 有什么(数据)?
    • __post_init__:初始化逻辑。在数据绑定完成后,执行所有必要的后续步骤。比如数据验证、调用其他方法等。
      • dataclass 做什么(操作)?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from typing import Dict, Any, List

@dataclass
class BaseTask(BaseCon):
# 步骤 1: 定义 __init__ 的参数
# 构造参数
# 这会生成 __init__(self, cfg: Dict, obj: Dict = None)
cfg: Dict = field(repr=False)
obj: Optional[Dict] = field(default=None, repr=False)

# 步骤 2: 定义所有内部状态属性,并用 init=False 排除
# 内部状态属性
precent: int = field(default=1, init=False)
hasend: bool = field(default=False, init=False)
# ... 其他几十个状态属性 ...

# 步骤 3: 把所有逻辑放入 __post_init__
# 复杂的初始化逻辑
def __post_init__(self):
# 先调用父类的初始化逻辑
super().__post_init__()

# 再执行本类的逻辑
if self.obj:
self.cfg.update(self.obj)
# ... 其他逻辑 ...

field

举个例子,对于 list,当复制它时只是复制了一份引用,所以像 dataclass 里那样直接复制给实例的做法的危险而错误的,为了保证使用 list 时的安全性,应该这样做:

1
2
3
4
5
@dataclass
class C:
mylist: List[int] = field(default_factory=list)

# 当初始化C的实例时就会调用list()而不是直接复制一份list的引用:

常用函数

1
2
3
4
5
6
7
8
9
10
11
12
>>> from dataclasses import asdict, astuple
>>> asdict(Lang())
{'name': 'python', 'strong_type': True, 'static_type': False, 'age': 28}
>>> astuple(Lang())
('python', True, False, 28)


>>> from dataclasses import is_dataclass
>>> is_dataclass(Lang)
True
>>> is_dataclass(Lang())
True

enum

enum:【标准库】枚举类

  • 一种数据类型,列出所有可能的取值。常用做一系列常量的集中处理,如月份、颜色
  • python3.4 及以上的版本才有
  • 优点:
    • 省内存:程序中是代码,内存中是 value,即数值,value:1 的占用比 name:red 的少
  • 要求:
    • 枚举类不能用来实例化对象,不可更改
    • 成员变量不重复
    • 成员变量值相同时,输出的结果都是前一个成员的
    • 若要不能定义相同的成员值,可以用 @unique 来修饰
    • 不能用 = 赋值,可以用 is 或 == 比较,但不能比较大小(IntEnum 时,INT 整数枚举可以相互比较)
    • Attention:两个地方写的相同代码的 Enum 类,导入后,== 比较的值不对,是多线程的坑还是它本身的坑?
1
2
3
4
5
6
7
8
9
10
from enum import Enum

# class syntax
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3

# functional syntax
Color = Enum('Color', [('RED', 1), ('GREEN', 2), ('BLUE', 3)])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from enum import Enum,unique
Month = Enum('Month',('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'))

for name,member in Month.__members__.items():
print(name,member,member.value)

@unique
class Color(Enum):
red=1
green=2
blue=3

# 取名称
Color.blue.name
Color['blue'].name

# 取值
Color.blue.value

# 值转为名称
a=1
Color(a)
# 比较:等值
result = Color.RED == Color.GREEN
# 但不能比较大小

Color.red # 通过成员名称取成员
Color(2) # 通过值取成员z

for color in Color:
print(color)
给博主来一杯卡布奇诺