Python 标准库进阶,进阶标准,一. 上下文管理1.
Python 标准库进阶,进阶标准,一. 上下文管理1.
一. 上下文管理
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@contextmanagerdef 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 closingfrom urllib.request import urlopenwith closing(urlopen('http://www.python.org')) as page: for line in page: print(line)# closing 等同于from contextlib import contextmanager@contextmanagerdef closing(thing): try: yield thing finally: thing.close() # 就是添加了一个自动 close 的功能
2.3 contextlib.suppress(*exceptions)
使 with 管理器抑制代码块内任何被指定的异常:
from contextlib import suppresswith 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 Pathdata_folder = Path("./source_data/text_files/")data_file = data_folder / "raw_data.txt" # Path 重载了 / 操作符,路径拼接超级方便# 路径的解析data_file.parent # 获取父路径,这里的结果就是 data_folderdata_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.txtdata_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 标准库进阶
评论关闭