一. 上下文管理
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 中的 * 表示任意字符, 而 ** 则表示任意层目录.(在大型文件树上使用 ** 速度会很慢!)
来源: http://www.bubuko.com/infodetail-2982287.html