mcpField
P
python-resource-management

python-resource-management

github.com/julianobarbosa开发1591 次安装2 天前更新
代码仓库

关于 python-resource-management

Python Resource Management 是面向需要可靠释放文件、连接、套接字等资源的开发者技能,介绍同步与异步上下文管理器、异常处理、流式处理及多资源清理模式。

主要功能

  1. 01使用同步和异步上下文管理器
  2. 02确保异常发生时仍清理资源
  3. 03用 ExitStack 管理多个资源
  4. 04处理流状态并跟踪指标
  5. 05高效累积字符串

适用场景

  1. 01可靠释放数据库连接
  2. 02清理文件和嵌套资源
  3. 03构建异步资源管理器
  4. 04处理流数据并测量耗时

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/julianobarbosa/claude-code-skills --skill python-resource-management

安装后在 Claude Code 中输入/python-resource-management确认已加载。

详细介绍

Python 资源管理

使用上下文管理器确定性地管理资源。数据库连接、文件句柄和网络套接字等资源应可靠释放,即使发生异常也不例外。

何时使用此技能

  • 管理数据库连接和连接池
  • 处理文件句柄和 I/O
  • 实现自定义上下文管理器
  • 构建带状态的流式响应
  • 处理嵌套资源的清理
  • 创建异步上下文管理器

核心概念

1. 上下文管理器

with 语句会确保资源自动释放,即使发生异常也不例外。

2. 协议方法

同步资源管理使用 __enter__/__exit__,异步资源管理使用 __aenter__/__aexit__。

3. 无条件清理

无论是否发生异常,都会运行 __exit__。

4. 异常处理

从 __exit__ 返回 True 可抑制异常,返回 False 则会继续传播异常。

快速入门

from contextlib import contextmanager

@contextmanager
def managed_resource():
    resource = acquire_resource()
    try:
        yield resource
    finally:
        resource.cleanup()

with managed_resource() as r:
    r.do_work()

基础模式

模式 1:基于类的上下文管理器

为复杂资源实现上下文管理器协议。

class DatabaseConnection:
    """Database connection with automatic cleanup."""

    def __init__(self, dsn: str) -> None:
        self._dsn = dsn
        self._conn: Connection | None = None

    def connect(self) -> None:
        """Establish database connection."""
        self._conn = psycopg.connect(self._dsn)

    def close(self) -> None:
        """Close connection if open."""
        if self._conn is not None:
            self._conn.close()
            self._conn = None

    def __enter__(self) -> "DatabaseConnection":
        """Enter context: connect and return self."""
        self.connect()
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        """Exit context: always close connection."""
        self.close()

# Usage with context manager (preferred)
with DatabaseConnection(dsn) as db:
    result = db.execute(query)

# Manual management when needed
db = DatabaseConnection(dsn)
db.connect()
try:
    result = db.execute(query)
finally:
    db.close()
模式 2:异步上下文管理器

对于异步资源,实现异步协议。

class AsyncDatabasePool:
    """Async database connection pool."""

    def __init__(self, dsn: str, min_size: int = 1, max_size: int = 10) -> None:
        self._dsn = dsn
        self._min_size = min_size
        self._max_size = max_size
        self._pool: asyncpg.Pool | None = None

    async def __aenter__(self) -> "AsyncDatabasePool":
        """Create connection pool."""
        self._pool = await asyncpg.create_pool(
            self._dsn,
            min_size=self._min_size,
            max_size=self._max_size,
        )
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        """Close all connections in pool."""
        if self._pool is not None:
            await self._pool.close()

    async def execute(self, query: str, *args) -> list[dict]:
        """Execute query using pooled connection."""
        async with self._pool.acquire() as conn:
            return await conn.fetch(query, *args)

# Usage
async with AsyncDatabasePool(dsn) as pool:
    users = await pool.execute("SELECT * FROM users WHERE active = $1", True)
模式 3:使用 @contextmanager 装饰器

对于简单场景,使用装饰器简化上下文管理器。

from contextlib import contextmanager, asynccontextmanager
import time
import structlog

logger = structlog.get_logger()

@contextmanager
def timed_block(name: str):
    """Time a block of code."""
    start = time.perf_counter()
    try:
        yield
    finally:
        elapsed = time.perf_counter() - start
        logger.info(f"{name} completed", duration_seconds=round(elapsed, 3))

# Usage
with timed_block("data_processing"):
    process_large_dataset()

@asynccontextmanager
async def database_transaction(conn: AsyncConnection):
    """Manage database transaction."""
    await conn.execute("BEGIN")
    try:
        yield conn
        await conn.execute("COMMIT")
    except Exception:
        await conn.execute("ROLLBACK")
        raise

# Usage
async with database_transaction(conn) as tx:
    await tx.execute("INSERT INTO users ...")
    await tx.execute("INSERT INTO audit_log ...")
模式 4:无条件释放资源

无论是否发生异常,都要在 __exit__ 中清理资源。

class FileProcessor:
    """Process file with guaranteed cleanup."""

    def __init__(self, path: str) -> None:
        self._path = path
        self._file: IO | None = None
        self._temp_files: list[Path] = []

    def __enter__(self) -> "FileProcessor":
        self._file = open(self._path, "r")
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        """Clean up all resources unconditionally."""
        # Close main file
        if self._file is not None:
            self._file.close()

        # Clean up any temporary files
        for temp_file in self._temp_files:
            try:
                temp_file.unlink()
            except OSError:
                pass  # Best effort cleanup

        # Return None/False to propagate any exception

高级模式

模式 5:有选择地抑制异常

仅抑制特定且有文档说明的异常。

class StreamWriter:
    """Writer that handles broken pipe gracefully."""

    def __init__(self, stream) -> None:
        self._stream = stream

    def __enter__(self) -> "StreamWriter":
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool:
        """Clean up, suppressing BrokenPipeError on shutdown."""
        self._stream.close()

        # Suppress BrokenPipeError (client disconnected)
        # This is expected behavior, not an error
        if exc_type is BrokenPipeError:
            return True  # Exception suppressed

        return False  # Propagate all other exceptions
模式 6:累积状态的流式处理

在流式处理中,同时维护增量数据块和累积状态。

from collections.abc import Generator
from dataclasses import dataclass, field

@dataclass
class StreamingResult:
    """Accumulated streaming result."""

    chunks: list[str] = field(default_factory=list)
    _finalized: bool = False

    @property
    def content(self) -> str:
        """Get accumulated content."""
        return "".join(self.chunks)

    def add_chunk(self, chunk: str) -> None:
        """Add chunk to accumulator."""
        if self._finalized:
            raise RuntimeError("Cannot add to finalized result")
        self.chunks.append(chunk)

    def finalize(self) -> str:
        """Mark stream complete and return content."""
        self._finalized = True
        return self.content

def stream_with_accumulation(
    response: StreamingResponse,
) -> Generator[tuple[str, str], None, str]:
    """Stream response while accumulating content.

    Yields:
        Tuple of (accumulated_content, new_chunk) for each chunk.

    Returns:
        Final accumulated content.
    """
    result = StreamingResult()

    for chunk in response.iter_content():
        result.add_chunk(chunk)
        yield result.content, chunk

    return result.finalize()
模式 7:高效累积字符串

累积字符串时,避免 O(n²) 的字符串拼接。

def accumulate_stream(stream) -> str:
    """Efficiently accumulate stream content."""
    # BAD: O(n²) due to string immutability
    # content = ""
    # for chunk in stream:
    #     content += chunk  # Creates new string each time

    # GOOD: O(n) with list and join
    chunks: list[str] = []
    for chunk in stream:
        chunks.append(chunk)
    return "".join(chunks)  # Single allocation
模式 8:跟踪流式处理指标

测量首字节时间和流式处理总耗时。

import time
from collections.abc import Generator

def stream_with_metrics(
    response: StreamingResponse,
) -> Generator[str, None, dict]:
    """Stream response while collecting metrics.

    Yields:
        Content chunks.

    Returns:
        Metrics dictionary.
    """
    start = time.perf_counter()
    first_chunk_time: float | None = None
    chunk_count = 0
    total_bytes = 0

    for chunk in response.iter_content():
        if first_chunk_time is None:
            first_chunk_time = time.perf_counter() - start

        chunk_count += 1
        total_bytes += len(chunk.encode())
        yield chunk

    total_time = time.perf_counter() - start

    return {
        "time_to_first_byte_ms": round((first_chunk_time or 0) * 1000, 2),
        "total_time_ms": round(total_time * 1000, 2),
        "chunk_count": chunk_count,
        "total_bytes": total_bytes,
    }
模式 9:使用 ExitStack 管理多个资源

简洁地处理动态数量的资源。

from contextlib import ExitStack, AsyncExitStack
from pathlib import Path

def process_files(paths: list[Path]) -> list[str]:
    """Process multiple files with automatic cleanup."""
    results = []

    with ExitStack() as stack:
        # Open all files - they'll all be closed when block exits
        files = [stack.enter_context(open(p)) for p in paths]

        for f in files:
            results.append(f.read())

    return results

async def process_connections(hosts: list[str]) -> list[dict]:
    """Process multiple async connections."""
    results = []

    async with AsyncExitStack() as stack:
        connections = [
            await stack.enter_async_context(connect_to_host(host))
            for host in hosts
        ]

        for conn in connections:
            results.append(await conn.fetch_data())

    return results

最佳实践总结

  1. 始终使用上下文管理器 - 用于任何需要清理的资源
  2. 无条件清理 - 即使发生异常,也会运行 __exit__
  3. 不要意外抑制异常 - 除非有意抑制,否则返回 False
  4. 使用 @contextmanager - 适用于简单的资源模式
  5. 实现两种协议 - 支持 with 和手动管理
  6. 使用 ExitStack - 适用于动态数量的资源
  7. 高效累积 - 使用列表 + join,而不是字符串拼接
  8. 跟踪指标 - 首字节时间对流式处理很重要
  9. 记录行为 - 尤其是异常抑制行为
  10. 测试清理路径 - 验证发生错误时资源是否已释放

注意事项

  • 生成器内部的 with open():文件会在生成器被 GC 回收时关闭,而不是在迭代结束时关闭 — 在 CPython 中(引用计数)没有问题,但在 PyPy/IronPython 中有风险。
  • ExitStack:对于 __enter__/__exit__ 协议,使用 enter_context;对于仅执行清理的回调,使用 push — 混用时若顺序错误,可能会在清理链中吞掉异常。
  • 异步上下文管理器必须使用 async with — 对异步管理器使用 with 会静默地返回协程,且不会执行清理。
  • 在 Windows 上,tempfile.NamedTemporaryFile 打开时无法重新打开 — 使用 delete=False 并显式清理是跨平台的做法。
  • 如果向 contextlib.suppress(Exception) 传入 BaseException,它也会吞掉 KeyboardInterrupt — 请明确指定要抑制的异常。

评论

支持 Markdown