个人笔记,基本都摘抄自 Python3 官方文档

一. 上下文管理

1. 传统的类方式

Java 使用 try 来自动管理资源,只要实现了 AutoCloseable 接口,就可以部分摆脱手动 colse 的地狱了。

而 Python,则是定义了两个 Protocol:__enter____exit__. 下面是一个 open 的模拟实现:

class OpenContext(object):

    def __init__(self, filename, mode):  # 调用 open(filename, mode) 返回一个实例
self.fp = open(filename, mode) def __enter__(self): # 用 with 管理 __init__ 返回的实例时,with 会自动调用这个方法
return self.fp # 退出 with 代码块时,会自动调用这个方法。
def __exit__(self, exc_type, exc_value, traceback):
self.fp.close() # 这里先构造了 OpenContext 实例,然后用 with 管理该实例
with OpenContext('/tmp/a', 'a') as f:
f.write('hello world')

这里唯一有点复杂的,就是 __exit__ 方法。和 Java 一样,__exit__ 相当于 try - catch - finallyfinally 代码块,在发生异常时,它也会被调用。

当没有异常发生时,__exit__ 的三个参数 exc_type, exc_value, traceback 都为 None,而当发生异常时,它们就对应异常的详细信息。

发生异常时, __exit__ 的返回值将被用于决定是否向外层抛出该异常,返回 True 则抛出,返回 False 则抑制(swallow it)。

Note 1:Python 3.6 提供了 async with 异步上下文管理器,它的 Protocol 和同步的 with 完全类似,是 __aenter____aexit__ 两个方法。

Note 2:与 Java 相同,with 支持同时管理多个资源,因此可以直接写 with open(x) as a, open(y) as b: 这样的形式。

2. contextlib

2.1 @contextlib.contextmanager

对于简单的 with 资源管理,编写一个类可能会显得比较繁琐,为此 contextlib 提供了一个方便的装饰器 @contextlib.contextmanager 用来简化代码。

使用它,上面的 OpenContext 可以改写成这样:

from contextlib import contextmanager
@contextmanager
def make_open_context(filename, mode):
fp = open(filename, mode)
try:
yield fp # 没错,这是一个生成器函数
finally:
fp.close() with make_open_context('/tmp/a', 'a') as f:
f.write('hello world')

使用 contextmanager 装饰一个生成器函数,yield 之前的代码对应 __enter__,finally 代码块就对应 __exit__.

Note:同样,也有异步版本的装饰器 @contextlib.asynccontextmanager

2.2 contextlib.closing(thing)

用于将原本不支持 with 管理的资源,包装成一个 Context 对象。

from contextlib import closing
from urllib.request import urlopen with closing(urlopen('http://www.python.org')) as page:
for line in page:
print(line) # closing 等同于
from contextlib import contextmanager @contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close() # 就是添加了一个自动 close 的功能

2.3 contextlib.suppress(*exceptions)

使 with 管理器抑制代码块内任何被指定的异常:

from contextlib import suppress

with suppress(FileNotFoundError):
os.remove('somefile.tmp') # 等同于
try:
os.remove('somefile.tmp')
except FileNotFoundError:
pass

2.4 contextlib.redirect_stdout(new_target)

将 with 代码块内的 stdout 重定向到指定的 target(可用于收集 stdout 的输出)

f = io.StringIO()
with redirect_stdout(f): # 将输出直接写入到 StringIO
help(pow)
s = f.getvalue() # 或者直接写入到文件
with open('help.txt', 'w') as f:
with redirect_stdout(f):
help(pow)

redirect_stdout 函数返回的 Context 是可重入的( reentrant),可以重复使用。

二、pathlib

提供了 OS 无关的文件路径抽象,可以完全替代 os.pathglob.

学会了 pathlib.Path,你就会了 Python 处理文件路径的所有功能。

1. 路径解析与拼接

from pathlib import Path

data_folder = Path("./source_data/text_files/")
data_file = data_folder / "raw_data.txt" # Path 重载了 / 操作符,路径拼接超级方便 # 路径的解析
data_file.parent # 获取父路径,这里的结果就是 data_folder
data_foler.parent # 会返回 Path("source_data")
data_file.parents[1] # 即获取到 data_file 的上上层目录,结果和上面一样是 Path("source_data")
data_file.parents[2] # 上上上层目录,Path(".") dara_file.name # 文件名 "raw_data.txt"
dara_file.suffix # 文件的后缀(最末尾的)".txt",还可用 suffixes 获取所有后缀 data_file.stem # 去除掉最末尾的后缀后(只去除一个),剩下的文件名:raw_data # 替换文件名或者文件后缀
data_file.with_name("test.txt") # 变成 .../test.txt
data_file.with_suffix(".pdf") # 变成 .../raw_data.pdf # 当前路径与另一路径 的相对路径
data_file.relative_to(data_folder) # PosixPath('raw_data.txt')

2. 常用的路径操作函数

if not data_folder.exists():
data_folder.mkdir(parents=True) # 直接创建文件夹,如果父文件夹不存在,也自动创建 if not filename.exists(): # 文件是否存在
filename.touch() # 直接创建空文件,或者用 filename.open() 直接获取文件句柄 # 路径类型判断
if data_file.is_file(): # 是文件
print(data_file, "is a file")
elif data_file.is_dir(): # 是文件夹
for child in p.iterdir(): # 通过 Path.iterdir() 迭代文件夹中的内容
print(child) # 路径解析
# 获取文件的绝对路径(符号链接也会被解析到真正的文件)
filename.resolve() # 在不区分大小写的系统上(Windows),这个函数也会将大小写转换成实际的形式。 # 可以直接获取 Home 路径或者当前路径
Path.home() / "file.txt" # 有时需要以 home 为 base path 来构建文件路径
Path.cwd() / "file.txt" # 或者基于当前路径构建

还有很多其它的实用函数,可在使用中慢慢探索。

3. glob

pathlib 也提供了 glob 支持,也就是广泛用在路径匹配上的一种简化正则表达式。

data_file.match(glob_pattern)  # 返回 True 或 False,表示文件路径与给出的 glob pattern 是否匹配

for py_file in data_folder.glob("*/*.py"):  # 匹配当前路径下的子文件夹中的 py 文件,会返回一个可迭代对象
print(py_file) # 反向匹配,相当于 glob 模式开头添加 "**/"
for py_file in data_folder.glob("**/*.py"): # 匹配当前路径下的所有 py 文件(所有子文件夹也会被搜索),返回一个可迭代对象
print(py_file)

glob 中的 * 表示任意字符,而 ** 则表示任意层目录。(在大型文件树上使用 ** 速度会很慢!)

三、functools

functools 提供了几个有时很有用的函数和装饰器

1. @functools.wraps

这个装饰器用于使装饰器 copy 被装饰的对象的 __module__, __name__, __qualname__, __annotations__ and __doc__ 属性,这样装饰器就显得更加透明。

from functools import wraps
def my_decorator(f):
@wraps(f)
def wrapper(*args, **kwds):
print('Calling decorated function')
return f(*args, **kwds)
return wrapper # 用了 wraps,wrapper 会复制 f 的各种文档属性 @my_decorator
def func(xx):
""" this is func's docstring"""
print("this is func~")

如果不用 wraps 的话,因为实际上返回的是 wrapper,被装饰对象的这些文档属性都会丢失。(比如 docstring)

因此在使用 wrapper 装饰器时,添加 @wraps() 装饰器是个好习惯。

2. functools.partial

这个感觉和高等数学的偏函数很像:比如函数 z = f(x, y) 有 x 和 y 两个变量,现在把 x 看作常数,就可以对 y 进行求导运算。

而 python 的 partial 也差不多,不过它不是把 x 看作常数,而是先给定 x 的值。用法如下:

from functools import partial
basetwo = partial(int, base=2) # 先给定 int 函数的 base 参数为 2
basetwo.__doc__ = 'Convert base 2 string to an int.' # 如果需要文档,可以添加 __doc__ 属性
basetwo('10010') # return 18

此外,还有个 partialmethod 函数,待了解

3. @functools.lru_cache(maxsize=128, typed=False)

如果某方法可能被频繁调用(使用相同的参数),而且它的结果在一定时间内不会改变。可以用 lru_cache 装饰它,减少运算量或 IO 操作。

from functools import lru_cache

# 缓存最近的(least recently used,lru) 64 次参数不同的调用结果。
@lru_cache(maxsize=64)
def my_sum(x): # 后续的调用中,如果参数能匹配到缓存,就直接返回缓存结果
return sum(x)

比如用递归计算斐波那契数列,数值较低的参数会被频繁使用,于是可以用 lru_cache 来缓存它们。

或者爬取网页,可能会需要频繁爬取一个变化不快的网页,这时完全可以用 cache 缓存。

但是它不能控制缓存失效时间,因此不能用于 Web 系统的缓存。还是得自己写个简单的装饰器,把缓存存到 redis 里并设置 expires。或者直接用 Flask 或 Django 的 caching 插件。

4. @functools.singledispatch

单重派发,即根据函数的第一个参数的类型,来决定调用哪一个同名函数。

@singledispatch
def parse(arg): # 首先定义一个默认函数
print('没有合适的类型被调用') # 如果参数类型没有匹配上,就调用这个默认函数 @parse.register(type(None)) # 第一个参数为 None
def _(arg):
print('出现 None 了') @parse.register(int) # 第一个参数为整数
def _(arg):
print('这次输入的是整数') @parse.register
def _(arg: list): # python3.7 开始,可以直接用类型注解来标注第一个参数的类型
print('这次输入的是列表')

画外:有单重派发,自然就有多重派发,Julia 语言就支持多重派发,即根据函数所有参数的类型,来决定调用哪一个同名函数。

Julia 语言根本没有类这个定义,类型的所有方法都是通过多重派发来定义的。

其他

  1. @functools.total_ordering:用于自动生成比较函数。
  2. functools.cmp_to_key(func):用于将老式的比较函数,转换成新式的 key 函数。

四、operator

operator 模块包含四种类型的方法:

1. operator.itemgetter

经常被用于 sorted/max/mix/itertools.groupby 等

使用方法:

# itemgetter
f = itemgetter(2)
f(r) # return r[2] # 还能一次获取多个值,像 numpy 那样索引
f2 = itemgetter(2,4,5)
f2(r) # return (r[2], r[4], r[5]) # 或者使用 slice 切片
s = itemgetter(slice(2, None))
s[r] # return r[2:] # dict 索引也能用
d = itemgetter('rank', 'name')
d[r] # return d['rank'], d['name']

用途:

# 用于指定用于比较大小的属性
key = itemgetter(1)
sorted(iterable, key=key) # 使用 iterable[1] 对 iterable 进行排序
max(iterable, key=key) # 找出最大的元素,使用 iterable[1] 做比较 # 用于高级切片(比如像 numpy 那样的,指定只获取某几列)
s = itemgetter(1,3,4)
matrix = [[0,1,2,3,4], [1,2,3,4,5]]
map(s, matrix) # list 后得到 [(1, 3, 4), (2,4,5)]

2. operator.attrgetter

可用于动态获取对象的属性,与直接用 getattr() 不同的是,它可以嵌套访问属性。

# 嵌套访问属性
att = attrgetter("a.b.c")
att(obj) # return obj.a.b.c # 和 itemgetter 一样,也可以一次获取多个属性
att = attrgetter("a.b.c", "x.y")
att(obj) # return (obj.a.b.c, obj.x.y) # 不嵌套的话,用 getattr 就行
getattr(obj, "a") # return obj.a

这里可以回顾一下类的两个魔法函数:

  1. __getattr__: 当被访问的属性不存在时,这个方法会被调用,它的返回值会成为对象的该属性。

    • 用于动态生成实例的属性/函数
  2. __getattribute__: 与 __getattr__ 唯一的差别在于,访问对象的任何属性,都会直接调用这个方法,不管属性存不存在

3. operator.methodcaller

可用于调用函数,它和 attrgetter 很像,差别在于 attrgetter 只是返回指定的属性,而 methodcaller 会直接把指定的属性当成函数调用,然后返回结果。

举例

f = methodcaller('name', 'foo', bar=1)
f(b) # returns b.name('foo', bar=1)

4. 各种操作符对应的函数

operator.add、operator.sub、operator.mul、operator.div 等等,函数式编程有时需要用到。

五、itertools

itertools 提供了许多针对可迭代对象的实用函数

方法很多,基本不可能一次全记住。还是要用到时多查吧。大致记住有提供哪些功能,需要用到时能想起可以查这个模块就行。

1. 无限迭代器

  1. count(start=0, step=1): 从 start 开始,每次迭代时,返回值都加一个 step

    • 默认返回序列为 0 1 2 3...
  2. cycle(iterable): 不断循环迭代 iterable
  3. repeat(element, times=None): 默认永远返回 element。(如果 times 不为 None,就迭代 times 后结束)

2. 排列组合迭代器

  1. product(p1, p2, ..., repeat=1):p1, p2... 的元素的笛卡尔积,相当于多层 for 循环

    • repeat 指参数重复次数,比如
>>> from itertools import product
>>> r = product([1, 2], [3, 4], [5, 6]) # 重复一次,也就是 (p1, p2, p3) 的笛卡尔积
>>> pprint(list(r))
[(1, 3, 5),
(1, 3, 6),
(1, 4, 5),
(1, 4, 6),
(2, 3, 5),
(2, 3, 6),
(2, 4, 5),
(2, 4, 6)]
>>> r2 = product([1, 2], [3, 4], [5, 6], repeat=2) # 重复两次,即 (p1, p2, p3, p1, p2, p3) 的笛卡尔积
>>> pprint(list(r2))
[(1, 3, 5, 1, 3, 5),
(1, 3, 5, 1, 3, 6),
(1, 3, 5, 1, 4, 5),
(1, 3, 5, 1, 4, 6),
(1, 3, 5, 2, 3, 5),
...
  1. permutations(p[, r]):p 中元素,长度为 r 的所有可能的排列。相当于 product 去重后的结果。
  2. combinations(p, r):既然有排列,当然就有组合了。

3. 其他

  1. zip_longest(*iterables, fillvalue=None):和 zip 的差别在于,缺失的元素它会用 fillvalue 补全,而不是直接结束。
  2. takewhile()
  3. dropwhile()
  4. groupby()

等等等,用得到的时候再查了。。。

六、collections

提供了一些实用的高级数据结构(容器)

  1. defaultdict:这个感觉是最常用的,可以给定 key 的默认值
  2. Counter:方便、快速的计数器。常用于分类统计
  3. deque:一个线程安全的双端队列
  4. OrderedDict:有时候会需要有序字典
  5. namedtuple:命名元组,有时用于参数传递。与 tuple 的差别是它提供了关键字参数和通过名字访问属性的功能
  6. ChainMap:将多个 map 连接(chain)在一起,提供一个统一的视图。因为是视图,所以原来的 map 不会被影响。

最新文章

  1. ucos实时操作系统学习笔记——内核结构和任务创建
  2. dmidecode常用参数
  3. textview 弹出键盘上面添加完成按钮,并设置输入内容的格式。
  4. ListView上拉加载,下拉刷新 PullToRefresh的使用
  5. 大并发连接的oracle在Linux下内存不足的问题的分析
  6. setsockopt
  7. 多线程GCD
  8. postgresql数据库的数据导出
  9. Fedora 17配置ssh及Windows远程连接
  10. 使用Pechkin将HTML网页转换为PDF
  11. 180114 用装饰器实现在不改变函数调用者的代码基础上,实现在函数执行前后分别打印"before" 和 "after"
  12. [Swift]LeetCode103. 二叉树的锯齿形层次遍历 | Binary Tree Zigzag Level Order Traversal
  13. 设计Optaplanner下实时规划服务的失败经历
  14. metasploit常见服务的漏点扫描模块
  15. 通过putty进行端口映射并且启动jupyter notebook
  16. react入门-refs
  17. modelsim编译Xilinx器件库的另一种方法(节省时间)
  18. error LNK2005: _DllMain@12 已经在 MSVCRTD.lib(dllmain.obj) 中定义
  19. Maven中央仓库——你可能不知道的细节
  20. jq对象和DOM对象的互换

热门文章

  1. linux 使用sqlite3
  2. 在react中实现CSS模块化
  3. java实现简单计算器功能
  4. 原生js方面的兼容性问题
  5. C++继承和派生练习(一)--关于从people(人员)类派生出student(学生)类等
  6. 【2017 World Final E】Need For Speed(二分)
  7. 【Nowcoder 上海五校赛】1 + 2 = 3?(斐波那契规律)
  8. ABAP术语-Authorization Check
  9. ansible-palybook剧本
  10. Java四舍五入时保留指定小数位数