1. 程式人生 > >Python 標準庫進階

Python 標準庫進階

osi cat 文件 操作 lose auto not trac 了解

一. 上下文管理

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 標準庫進階