Python 标准库进阶

Posted 於清樂的碎碎念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 标准库进阶相关的知识,希望对你有一定的参考价值。

一. 上下文管理

1. 传统的类方式

Java 使用 try 来自动管理资源,只要实现了 AutoCloseable 接口,就可以部分摆脱手动 colse 的地狱了。
而 Python,则是定义了两个 Protocol:enterexit. 下面是一个 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 就是你需要了解的所有内容。

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 标准库进阶的主要内容,如果未能解决你的问题,请参考以下文章

Python 标准库一览(Python进阶学习)

Python入门自学进阶——3--模块相关概念

我的OpenGL学习进阶之旅NDK开发中find_library查找的系统动态库在哪里?

我的OpenGL学习进阶之旅NDK开发中find_library查找的系统动态库在哪里?

有哪些比较好的讲Python进阶的博客?

进阶第十六课 Python模块之Pandas