0

0

Python异步类构造器的最佳实践:避免__init__中的异步操作

霞舞

霞舞

发布时间:2025-12-05 11:16:02

|

857人浏览过

|

来源于php中文网

原创

Python异步类构造器的最佳实践:避免__init__中的异步操作

python中,`__init__`方法不能直接使用`await`关键字执行异步操作。尝试在构造器中强制执行异步代码会导致性能问题或ide警告。本文将探讨为什么应避免在`__init__`中进行异步初始化,并介绍使用异步工厂方法作为最佳实践,以确保类能够被轻松构造,同时实现异步资源的正确初始化。

引言:__init__与异步编程的冲突

在Python的异步编程模型中,async def函数是协程,它们能够暂停执行并等待其他异步操作完成。然而,类的构造方法__init__是一个同步方法,Python语言规范不允许将其定义为async def __init__。这意味着任何尝试在__init__内部直接使用await关键字的代码都会导致语法错误。

例如,在一个需要与Cosmos数据库进行交互的FastAPI项目中,我们可能希望在创建数据库操作类实例时,就确保数据库和容器已经存在:

from azure.cosmos.aio import CosmosClient # 假设这是一个异步客户端

class CosmosCRUD:
    def __init__(self, client: CosmosClient):
        self.client = client
        # 以下代码会导致语法错误,因为__init__不能await
        # self.database = await self.client.create_database_if_not_exists("MY_DATABASE_NAME")
        # self.container = await self.database.create_container_if_not_exists("MY_CONTAINER_NAME", partition_key=...)

面对这一限制,开发者可能会考虑以下几种方案:

  1. 在__init__内创建新的事件循环并运行异步代码:这种方法会阻塞主事件循环,尤其是在每次实例化时都执行,将严重影响整个应用程序的性能。
  2. 忽略__init__,创建独立的异步初始化方法:虽然可以实现功能,但IDE(如PyCharm)可能无法正确识别在__init__之外定义的实例属性,导致类型检查警告。
  3. 尝试依赖注入:虽然依赖注入在FastAPI等框架中非常有用,但它主要用于提供外部依赖,而不是解决类自身内部的异步初始化逻辑。

这些方案都存在各自的缺陷,通常不被推荐。

立即学习Python免费学习笔记(深入)”;

为何构造器应保持同步与简洁

一个核心的设计原则是:类应可轻易构造(trivially constructible)。这意味着__init__方法应该只负责初始化实例的基本状态,例如接收并存储必要的参数,声明实例属性,而不应执行耗时、复杂或具有副作用的操作。将异步操作放入__init__会带来以下问题:

  • 阻塞主事件循环:如果强行在同步__init__中运行异步代码(例如通过asyncio.run()或loop.run_until_complete()),会导致当前线程被阻塞,影响整个应用程序的响应性。
  • 不一致的状态:如果异步初始化失败,实例可能处于一个不完整的或无效的状态,难以处理。
  • 可测试性差:带有复杂异步逻辑的构造器会增加单元测试的难度。
  • 违反预期:使用者通常期望构造器是快速且同步的,突然的异步行为会打破这种预期。

推荐方案:异步工厂方法

处理类中异步初始化逻辑的最佳实践是使用异步工厂方法(Asynchronous Factory Method)。这种模式将对象的创建和异步初始化逻辑清晰地分离。

得到AI工具箱
得到AI工具箱

发现好用的AI工具

下载

核心思想:

  1. 同步__init__:__init__方法保持同步,仅用于接收和存储必要的参数,并声明实例的属性(可以初始化为None或默认值)。这解决了IDE警告问题。
  2. 异步工厂方法:提供一个async的类方法(通常命名为create、build或from_config等),它负责:
    • 调用同步__init__来创建类的实例。
    • 执行所有必要的异步初始化操作(如连接数据库、创建资源)。
    • 返回完全初始化好的实例。

示例代码:使用异步工厂方法初始化CosmosCRUD类

from azure.cosmos.aio import CosmosClient, DatabaseProxy, ContainerProxy
import asyncio

class CosmosCRUD:
    """
    Cosmos DB CRUD操作类,采用异步工厂模式进行初始化。
    """
    def __init__(self, client: CosmosClient, database_name: str, container_name: str, partition_key_path: str):
        """
        同步构造器,仅用于声明和存储基本属性。
        异步资源在此处声明为None,等待异步工厂方法进行初始化。
        """
        self.client: CosmosClient = client
        self.database_name: str = database_name
        self.container_name: str = container_name
        self.partition_key_path: str = partition_key_path
        self.database: DatabaseProxy = None  # 声明类型,但初始为None
        self.container: ContainerProxy = None # 声明类型,但初始为None
        print("CosmosCRUD: __init__ called (synchronous part)")

    @classmethod
    async def create(cls, client: CosmosClient, database_name: str, container_name: str, partition_key_path: str):
        """
        异步工厂方法,负责创建实例并执行异步初始化逻辑。
        """
        # 1. 调用同步__init__创建实例
        instance = cls(client, database_name, container_name, partition_key_path)
        # 2. 执行异步初始化
        await instance._async_initialize_resources()
        return instance

    async def _async_initialize_resources(self):
        """
        内部异步初始化方法,处理所有异步资源创建和设置。
        """
        print(f"CosmosCRUD: _async_initialize_resources - Ensuring database '{self.database_name}' exists...")
        self.database = await self.client.create_database_if_not_exists(self.database_name)

        print(f"CosmosCRUD: _async_initialize_resources - Ensuring container '{self.container_name}' exists...")
        self.container = await self.database.create_container_if_not_exists(
            self.container_name, partition_key=self.partition_key_path
        )
        print("CosmosCRUD: Asynchronous resources initialized successfully.")

    async def create_item(self, item: dict):
        """示例:创建Cosmos DB文档"""
        if not self.container:
            raise RuntimeError("Cosmos container not initialized.")
        print(f"CosmosCRUD: Creating item with id '{item.get('id')}'...")
        return await self.container.create_item(item)

    async def read_item(self, item_id: str, partition_key_value: str):
        """示例:读取Cosmos DB文档"""
        if not self.container:
            raise RuntimeError("Cosmos container not initialized.")
        print(f"CosmosCRUD: Reading item '{item_id}'...")
        return await self.container.read_item(item_id, partition_key=partition_key_value)

    async def close(self):
        """清理资源,例如关闭CosmosClient连接"""
        if self.client:
            print("CosmosCRUD: Closing CosmosClient...")
            await self.client.close()
            self.client = None # 清理引用
            print("CosmosClient closed.")

# --- 模拟CosmosClient及其相关代理类以便于运行示例 ---
class MockContainerProxy:
    def __init__(self, name):
        self.name = name
    async def create_item(self, item):
        print(f"Mock: Created item {item.get('id')} in container '{self.name}'")
        return item
    async def read_item(self, item_id, partition_key):
        print(f"Mock: Reading item '{item_id}' from container '{self.name}' (pk: {partition_key})")
        return {"id": item_id, "data": "mock_data", "pk": partition_key}

class MockDatabaseProxy:
    def __init__(self, name):
        self.name = name
    async def create_container_if_not_exists(self, name, partition_key):
        print(f"Mock: Creating/getting container '{name}' (partition_key: {partition_key})")
        return MockContainerProxy(name)

class MockCosmosClient:
    async def create_database_if_not_exists(self, name):
        print(f"Mock: Creating/getting database '{name}'")
        return MockDatabaseProxy(name)
    async def close(self):
        print("Mock: CosmosClient closed.")

# --- 示例用法 ---
async def main():
    print("\n--- Starting main execution ---")
    mock_client = MockCosmosClient()

    # 使用异步工厂方法创建CosmosCRUD实例
    print("\nAttempting to create CosmosCRUD instance...")
    cosmos_crud_instance = await CosmosCRUD.create(
        client=mock_client,
        database_name="MyFastAPIDB",
        container_name="UserItems",
        partition_key_path="/id"
    )
    print("CosmosCRUD instance successfully created and initialized.")

    # 现在可以使用实例的异步方法
    new_item = {"id": "user123", "name": "Alice", "city": "New York"}
    await cosmos_crud_instance.create_item(new_item)

    retrieved_item = await cosmos_crud_instance.read_item("user123", "user123")
    print(f"Retrieved item: {retrieved_item}")

    # 清理资源
    await cosmos_crud_instance.close()
    print("--- Main execution finished ---")

if __name__ == "__main__":
    asyncio.run(main())

优点:

  • 符合Python规范:__init__保持同步,避免了语法错误和潜在的阻塞问题。
  • IDE友好:__init__中声明了所有实例属性,即使它们在异步工厂中被赋值,IDE也能正确识别这些属性的类型,减少误报。
  • 清晰的职责分离:对象创建(__init__)与异步资源初始化(create方法)的职责明确分开。
  • 非阻塞:异步初始化在事件循环中以非阻塞方式执行,不会影响应用程序的整体性能。
  • 更强的控制力:在异步初始化过程中可以进行更复杂的逻辑判断和错误处理。

结合FastAPI与依赖注入的考量

在FastAPI等Web框架中,依赖注入(Dependency Injection, DI)是一种强大的模式,用于管理和提供请求处理函数所需的资源。FastAPI的依赖项可以被定义为async函数,从而能够异步地获取数据库连接、验证用户等。

虽然依赖注入可以解决如何将一个已初始化的CosmosCRUD实例提供给FastAPI路由的问题,但它通常不直接解决CosmosCRUD类自身内部的异步初始化问题。如果CosmosCRUD实例在被创建后需要执行异步操作来设置其内部状态(如创建数据库/容器),那么异步工厂模式仍然是类设计的首选。

例如,在FastAPI中,你可能会这样使用:

from fastapi import FastAPI, Depends
# ... 其他导入和 CosmosCRUD 类定义 ...

app = FastAPI()

# 异步依赖项,用于创建并提供CosmosCRUD实例
async def get_cosmos_crud_instance():
    mock_client = MockCosmosClient() # 实际应用中会是真实的CosmosClient
    cosmos_crud = await CosmosCRUD.create(
        client=mock_client,
        database_name="MyFastAPIDB",
        container_name="UserItems",
        partition_key_path="/id"
    )
    try:
        yield cosmos_crud
    finally:
        await cosmos_crud.close() # 确保资源被清理

@app.get("/users/{user_id}")
async def read_user(user_id: str, cosmos: CosmosCRUD = Depends(get_cosmos_crud_instance)):
    user = await cosmos.read_item(user_id, user_id)
    return {"user": user}

# 运行FastAPI应用: uvicorn your_module_name:app --reload

在这种场景下,FastAPI的依赖注入机制与异步工厂模式完美结合,实现了异步资源的生命周期管理。

注意事项与最佳实践

  1. 资源清理:对于数据库连接、文件句柄等异步资源,务必在不再需要时进行清理。可以为类提供一个async def close()方法,并在使用完毕后调用它。更健壮的方式是实现异步上下文管理器 (async with),通过__aenter__和__aexit__来管理资源的获取和释放。
  2. 错误处理:在异步工厂方法和内部初始化方法中,应加入适当的错误处理逻辑,以应对网络问题、权限不足等异常情况,确保程序健壮性。
  3. 命名约定:异步工厂方法通常命名为create、build、from_config等,清晰表达其创建并初始化实例的意图。
  4. 避免过度初始化:只在确实需要时才执行异步初始化。如果一个类的大部分方法都不依赖于异步资源,可以考虑将异步资源的初始化推迟到第一次使用时(懒加载),但这会增加代码复杂性。通常,异步工厂模式是更直接和可预测的方案。

总结

在Python中,__init__方法不能直接包含异步操作。为了在类实例化时进行异步资源初始化,最推荐且优雅的解决方案是采用异步工厂方法。这种模式通过一个同步的__init__方法进行属性声明,并结合一个async类方法来执行所有的异步设置,从而确保了代码的规范性、可读性、可维护性,并避免了性能问题和IDE警告。结合FastAPI等框架的依赖注入机制,可以实现异步资源的高效且生命周期可控的管理。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Python FastAPI异步API开发_Python怎么用FastAPI构建异步API
Python FastAPI异步API开发_Python怎么用FastAPI构建异步API

Python FastAPI 异步开发利用 async/await 关键字,通过定义异步视图函数、使用异步数据库库 (如 databases)、异步 HTTP 客户端 (如 httpx),并结合后台任务队列(如 Celery)和异步依赖项,实现高效的 I/O 密集型 API,显著提升吞吐量和响应速度,尤其适用于处理数据库查询、网络请求等耗时操作,无需阻塞主线程。

28

2025.12.22

Python 微服务架构与 FastAPI 框架
Python 微服务架构与 FastAPI 框架

本专题系统讲解 Python 微服务架构设计与 FastAPI 框架应用,涵盖 FastAPI 的快速开发、路由与依赖注入、数据模型验证、API 文档自动生成、OAuth2 与 JWT 身份验证、异步支持、部署与扩展等。通过实际案例,帮助学习者掌握 使用 FastAPI 构建高效、可扩展的微服务应用,提高服务响应速度与系统可维护性。

249

2026.02.06

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

723

2023.08.10

pycharm怎么改成中文
pycharm怎么改成中文

PyCharm是一种Python IDE(Integrated Development Environment,集成开发环境),带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试、语法高亮、项目管理、代码跳转、智能提示、自动完成、单元测试、版本控制。此外,该IDE提供了一些高级功能,以用于支持Django框架下的专业Web开发。php中文网给大家带来了pycharm相关的教程以及文章,欢迎大家前来学习和阅读。

229

2023.07.25

pycharm安装教程
pycharm安装教程

PyCharm是一款由JetBrains开发的Python集成开发环境(IDE),它提供了许多方便的功能和工具。本专题为大家带来pycharm安装教程,帮助大家解决问题。

213

2023.08.21

如何解决pycharm找不到模块
如何解决pycharm找不到模块

解决pycharm找不到模块的方法:1、检查python解释器;2、安装缺失的模块;3、检查项目结构;4、检查系统路径;5、使用虚拟环境;6、重启PyCharm或电脑。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

664

2023.12.04

如何安装pycharm
如何安装pycharm

安装pycharm的步骤:1、访问PyCharm官方网站下载最新版本的PyCharm;2、下载完成后,打开安装文件;3、安装完成后,打开PyCharm;4、在PyCharm的主界面中等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

154

2024.02.23

python和pycharm的区别
python和pycharm的区别

Python和PyCharm是两个不同的概念,它们的区别如下:1、Python是一种编程语言,而PyCharm是一款Python集成开发环境;2、Python可以运行在各种不同的开发环境中,而PyCharm是专门为Python开发而设计的IDE等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

297

2024.02.23

Golang 测试体系与代码质量保障:工程级可靠性建设
Golang 测试体系与代码质量保障:工程级可靠性建设

Go语言测试体系与代码质量保障聚焦于构建工程级可靠性系统。本专题深入解析Go的测试工具链(如go test)、单元测试、集成测试及端到端测试实践,结合代码覆盖率分析、静态代码扫描(如go vet)和动态分析工具,建立全链路质量监控机制。通过自动化测试框架、持续集成(CI)流水线配置及代码审查规范,实现测试用例管理、缺陷追踪与质量门禁控制,确保代码健壮性与可维护性,为高可靠性工程系统提供质量保障。

48

2026.02.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 4.7万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号