0

0

Python中异步类构造的最佳实践:避免在__init__中使用await

聖光之護

聖光之護

发布时间:2025-12-06 23:21:01

|

411人浏览过

|

来源于php中文网

原创

Python中异步类构造的最佳实践:避免在__init__中使用await

python中,`__init__`方法不能直接包含`await`操作,因为它是同步的。本文将探讨为什么应避免在构造函数中执行异步逻辑,并提供一种推荐的解决方案——使用异步工厂方法模式来初始化需要异步资源(如数据库连接)的类,同时解决ide关于未初始化变量的警告,确保代码的健壮性和可维护性。

异步操作与Python构造函数的限制

Python的__init__方法是一个同步方法,其设计目的是用于初始化对象的状态,而非执行可能需要等待外部I/O的操作。这意味着,任何涉及await关键字的异步操作都无法直接在__init__中执行,否则会引发SyntaxError。

尝试在__init__中直接使用await,例如连接数据库、创建资源等,是常见的初学者困惑。然而,这种做法不仅在语法上不被允许,从软件设计的角度来看也并非最佳实践。一个良好的类构造函数应该能够快速、无副作用地完成对象的实例化,使其成为“可简单构造”的(trivially constructible)。将耗时的异步操作放入构造函数会违反这一原则,可能导致应用程序启动缓慢、资源管理复杂化,甚至在某些并发场景下引发不可预测的问题。

为什么避免在__init__中执行异步逻辑

  1. 语法限制: Python语言规定__init__不能是async def函数,因此无法直接使用await。
  2. 设计原则: 构造函数的职责是初始化对象,而不是执行业务逻辑或耗时的I/O操作。一个对象应该在构造完成后立即可用,即使其内部资源尚未完全准备好。
  3. 性能影响: 如果在构造函数中强制执行异步操作(例如通过创建新的事件循环),可能会阻塞主事件循环,尤其是在Web框架(如FastAPI)中,这将严重影响请求处理性能。
  4. 资源管理: 在构造函数中创建异步资源(如数据库连接)使得资源的正确关闭和生命周期管理变得复杂。

推荐解决方案:异步工厂方法模式

解决在类初始化时执行异步操作的最佳实践是采用异步工厂方法模式。这种模式将异步初始化逻辑从__init__中分离出来,放入一个单独的异步类方法或静态方法中。

核心思想

  1. __init__方法保持同步且轻量,仅用于初始化对象的内部状态(如成员变量的声明)。
  2. 创建一个异步的类方法(通常命名为create、build或from_config),它负责执行所有异步初始化步骤。
  3. 这个异步工厂方法首先调用__init__来创建对象实例,然后对该实例执行所有必要的异步设置,最后返回完全初始化的实例。

示例代码:使用异步工厂方法初始化CosmosDB客户端

假设我们有一个CosmosCRUD类,需要异步地确保Cosmos DB数据库和容器的存在。

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

Vondy
Vondy

下一代AI应用平台,汇集了一流的工具/应用程序

下载
import asyncio
from azure.cosmos.aio import CosmosClient, DatabaseProxy, ContainerProxy
from typing import Optional

# 假设这些是你的Cosmos DB配置
COSMOS_DB_NAME = "MY_DATABASE_NAME"
COSMOS_CONTAINER_NAME = "MY_CONTAINER_NAME"
# 假设 partition_key 是一个字典或字符串,例如 {"path": "/id"}
COSMOS_PARTITION_KEY = {"path": "/id"} 

class CosmosCRUD:
    """
    用于执行Cosmos DB CRUD操作的异步类。
    使用异步工厂方法进行初始化。
    """

    # 声明实例变量,以便IDE能够识别它们
    # 在__init__中可以赋None,表示它们将在异步工厂方法中被初始化
    client: CosmosClient
    database: DatabaseProxy
    container: ContainerProxy

    def __init__(self, client: CosmosClient):
        """
        同步构造函数,仅用于接收CosmosClient实例。
        不执行任何异步操作。
        """
        self.client = client
        # 可以在这里初始化为None,明确表示它们稍后会被赋值
        self.database = None 
        self.container = None

    @classmethod
    async def create(cls, client: CosmosClient) -> "CosmosCRUD":
        """
        异步工厂方法,负责CosmosCRUD实例的异步初始化。
        确保数据库和容器的存在。
        """
        # 1. 调用同步构造函数创建实例
        instance = cls(client)

        # 2. 执行所有异步初始化逻辑
        # 确保数据库存在
        instance.database = await instance.client.create_database_if_not_exists(COSMOS_DB_NAME)

        # 确保容器存在
        instance.container = await instance.database.create_container_if_not_exists(
            COSMOS_CONTAINER_NAME, 
            partition_key=COSMOS_PARTITION_KEY
        )

        print(f"Cosmos DB '{COSMOS_DB_NAME}' and container '{COSMOS_CONTAINER_NAME}' ensured.")

        # 3. 返回完全初始化的实例
        return instance

    async def create_item(self, item: dict) -> dict:
        """
        示例:创建Cosmos DB文档
        """
        if not self.container:
            raise RuntimeError("CosmosCRUD not properly initialized. Container is missing.")
        response = await self.container.create_item(body=item)
        print(f"Item created: {response}")
        return response

    async def read_item(self, item_id: str, partition_key_value: str) -> Optional[dict]:
        """
        示例:读取Cosmos DB文档
        """
        if not self.container:
            raise RuntimeError("CosmosCRUD not properly initialized. Container is missing.")
        try:
            # 注意:读取操作需要提供partition_key
            response = await self.container.read_item(item=item_id, partition_key=partition_key_value)
            print(f"Item read: {response}")
            return response
        except Exception as e:
            print(f"Error reading item {item_id}: {e}")
            return None

# 假设你已经有了一个CosmosClient实例
# 这是一个模拟的CosmosClient,实际项目中应使用azure.cosmos.aio.CosmosClient
class MockCosmosClient:
    async def create_database_if_not_exists(self, db_name: str):
        print(f"Mock: Ensuring database '{db_name}' exists.")
        return MockDatabaseProxy(db_name)

class MockDatabaseProxy:
    def __init__(self, db_name: str):
        self.db_name = db_name
    async def create_container_if_not_exists(self, container_name: str, partition_key: dict):
        print(f"Mock: Ensuring container '{container_name}' exists with key {partition_key}.")
        return MockContainerProxy(container_name)

class MockContainerProxy:
    def __init__(self, container_name: str):
        self.container_name = container_name
        self._items = {}
    async def create_item(self, body: dict) -> dict:
        import uuid
        item_id = str(uuid.uuid4())
        body["id"] = item_id # Cosmos DB items usually have an 'id'
        self._items[item_id] = body
        print(f"Mock: Created item with id '{item_id}'.")
        return body
    async def read_item(self, item: str, partition_key: str) -> dict:
        # Simplified mock for read_item
        if item in self._items and self._items[item].get(COSMOS_PARTITION_KEY["path"].strip('/')) == partition_key:
            return self._items[item]
        raise Exception("Item not found or partition key mismatch.")

async def main():
    # 实际应用中,这里会是真正的CosmosClient实例
    # from azure.cosmos.aio import CosmosClient
    # cosmos_client = CosmosClient(url=COSMOS_DB_URL, credential=COSMOS_DB_KEY)
    mock_cosmos_client = MockCosmosClient() 

    print("Initializing CosmosCRUD instance...")
    crud_instance = await CosmosCRUD.create(mock_cosmos_client)
    print("CosmosCRUD instance initialized.")

    # 现在可以使用crud_instance进行操作
    new_item = {"name": "Test Item", "description": "This is a test.", "id": "item1", "category": "books"}
    created_item = await crud_instance.create_item(new_item)

    # 假设partition_key_value是"item1" (对应COSMOS_PARTITION_KEY={"path": "/id"})
    read_item = await crud_instance.read_item("item1", "item1")

    # 假设在FastAPI应用中,CosmosClient通常会在应用启动时创建和关闭
    # await mock_cosmos_client.close() # 实际客户端需要关闭

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

解决IDE关于未初始化变量的警告

在上述示例中,我们通过两种方式解决了IDE(如PyCharm)可能发出的关于self.database和self.container未在__init__中初始化的警告:

  1. 类型注解(Type Hints): 在类定义中明确声明实例变量的类型,例如 database: DatabaseProxy。这告诉IDE这些属性将存在且具有特定类型。
  2. __init__中初始化为None: 在__init__中将这些变量显式初始化为None(例如 self.database = None)。这使得变量在对象创建时就存在,只是其值暂时为None,待异步工厂方法完成后才会被赋予实际值。IDE会识别这种模式,并减少误报。

集成到FastAPI等异步框架

在FastAPI这样的异步Web框架中,这种异步工厂模式尤其有用。你可以在应用启动时(例如使用@app.on_event("startup"))创建并初始化这些需要异步资源的类实例,然后通过依赖注入将其提供给路由处理函数。

# FastAPI 示例伪代码
from fastapi import FastAPI, Depends
from azure.cosmos.aio import CosmosClient # 假设是真实的客户端

app = FastAPI()

# 全局变量来存储初始化后的CRUD实例
cosmos_crud_instance: Optional[CosmosCRUD] = None
global_cosmos_client: Optional[CosmosClient] = None

@app.on_event("startup")
async def startup_event():
    global global_cosmos_client, cosmos_crud_instance
    # 实际应用中,这里应根据配置创建CosmosClient
    # 例如:global_cosmos_client = CosmosClient(url=..., credential=...)
    global_cosmos_client = MockCosmosClient() # 使用Mock客户端
    cosmos_crud_instance = await CosmosCRUD.create(global_cosmos_client)
    print("FastAPI startup: CosmosCRUD instance ready.")

@app.on_event("shutdown")
async def shutdown_event():
    global global_cosmos_client
    if global_cosmos_client:
        # 实际客户端需要关闭
        # await global_cosmos_client.close() 
        print("FastAPI shutdown: CosmosClient closed.")

async def get_cosmos_crud() -> CosmosCRUD:
    """依赖注入函数,提供CosmosCRUD实例"""
    if cosmos_crud_instance is None:
        raise RuntimeError("CosmosCRUD instance not initialized.")
    return cosmos_crud_instance

@app.get("/items/{item_id}")
async def get_item(item_id: str, crud: CosmosCRUD = Depends(get_cosmos_crud)):
    # 假设分区键与item_id相同
    item = await crud.read_item(item_id, item_id) 
    if item:
        return {"message": "Item found", "item": item}
    return {"message": "Item not found"}, 404

@app.post("/items/")
async def create_new_item(item_data: dict, crud: CosmosCRUD = Depends(get_cosmos_crud)):
    created_item = await crud.create_item(item_data)
    return {"message": "Item created", "item": created_item}

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

总结与注意事项

  • 坚守原则: 始终将__init__视为同步且轻量的初始化方法,避免在其内部执行任何await操作。
  • 使用异步工厂: 当类需要异步资源或复杂的异步设置时,异步工厂方法是最佳选择。它清晰地分离了对象的创建和异步初始化逻辑。
  • IDE友好: 结合类型注解和在__init__中将异步初始化的成员变量赋None,可以有效解决IDE的警告问题,提高代码可读性和可维护性。
  • 资源管理: 在应用生命周期中(如FastAPI的启动/关闭事件),妥善管理异步资源的创建和关闭,确保连接池等资源得到正确释放。

通过遵循这些最佳实践,你可以在Python中优雅地构建包含异步逻辑的类,同时保持代码的清晰、高效和健壮性。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

753

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

636

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

758

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

618

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1262

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

547

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

577

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

707

2023.08.11

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

36

2026.01.14

热门下载

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

精品课程

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

共4课时 | 0.7万人学习

Django 教程
Django 教程

共28课时 | 3.1万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.1万人学习

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

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