Python 标准库进阶
Posted 於清樂的碎碎念
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 标准库进阶相关的知识,希望对你有一定的参考价值。
一. 上下文管理
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 - finally
的 finally
代码块,在发生异常时,它也会被调用。
当没有异常发生时,__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.path
和 glob
.
基本上,pathlib.Path
就是你需要了解的所有内容。
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.exist():
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() # 获取文件的绝对路径(符号链接也会被解析到真正的文件)
# 可以直接获取 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 中的 * 表示任意字符,而 ** 则表示任意层目录。(在大型文件树上使用 ** 速度会很慢!)
以上是关于Python 标准库进阶的主要内容,如果未能解决你的问题,请参考以下文章
我的OpenGL学习进阶之旅NDK开发中find_library查找的系统动态库在哪里?