You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python中为含异步方法的类实现Fluent pattern?

问题描述

现有如下Python类,包含同步方法isHuman与异步方法hasJob、isKnight:

from typing import Self
import asyncio

class Character:
    def __init__(self, name: str, human: bool):
        self.name = name
        self.human = human

    def isHuman(self) -> Self:
        if self.human:
            return self
        raise Exception(f'{self.name} is not human')

    async def hasJob(self) -> Self:
        await asyncio.sleep(1)
        return self

    async def isKnight(self) -> Self:
        await asyncio.sleep(1)
        return self

当所有方法为同步时,可通过Fluent模式实现链式调用:

# 同步链式调用示例
john = (
    Character("John", human=True)
    .isHuman()
    .hasJob()
    .isKnight()
)

目前异步方法只能分步调用:

# 现有异步调用方式
john = Character("John", human=True).isHuman()
await john.hasJob()
await john.isKnight()

期望实现类似同步的链式调用写法,通过一次await得到最终实例:

# 目标异步链式调用写法
john = await (
    Character("John", human=True)
    .isHuman()
    .hasJob()
    .isKnight()
)
解决方案

核心思路是让异步方法返回的对象同时支持链式调用和被await执行,以下提供两种可行实现方式:

方案一:通用包装类封装调用链

创建一个AsyncFluentWrapper类,负责拦截方法调用、处理同步/异步逻辑,并实现__await__方法让自身可被await:

from typing import Any, Coroutine
import asyncio

class AsyncFluentWrapper:
    def __init__(self, instance: Any):
        self._instance = instance

    def __getattr__(self, name: str) -> Any:
        attr = getattr(self._instance, name)
        if asyncio.iscoroutinefunction(attr):
            # 异步方法:执行后返回新的包装器
            async def wrapped(*args, **kwargs):
                result = await attr(*args, **kwargs)
                return AsyncFluentWrapper(result)
            return wrapped
        else:
            # 同步方法:执行后更新实例并返回自身维持链式
            def wrapped(*args, **kwargs):
                self._instance = attr(*args, **kwargs)
                return self
            return wrapped

    def __await__(self):
        # 让包装器可被await,最终返回原始实例
        async def _await():
            return self._instance
        return _await().__await__()

# 修改Character的isHuman方法,返回包装器
class Character:
    def __init__(self, name: str, human: bool):
        self.name = name
        self.human = human

    def isHuman(self) -> AsyncFluentWrapper:
        if self.human:
            return AsyncFluentWrapper(self)
        raise Exception(f'{self.name} is not human')

    async def hasJob(self) -> Self:
        await asyncio.sleep(1)
        return self

    async def isKnight(self) -> Self:
        await asyncio.sleep(1)
        return self

现在即可按目标方式调用:

async def main():
    john = await (
        Character("John", human=True)
        .isHuman()
        .hasJob()
        .isKnight()
    )
    print(john.name)  # 输出: John

asyncio.run(main())

方案二:直接修改原类异步方法

如果不想额外引入包装类,可直接改造异步方法,让其返回一个兼具链式调用和可await特性的内部类实例:

from typing import Self, Coroutine
import asyncio

class Character:
    def __init__(self, name: str, human: bool):
        self.name = name
        self.human = human

    def isHuman(self) -> Self:
        if self.human:
            return self
        raise Exception(f'{self.name} is not human')

    # 原始异步逻辑
    async def _hasJob(self) -> Self:
        await asyncio.sleep(1)
        return self

    # 对外暴露的链式方法
    def hasJob(self):
        class Wrapper:
            def __init__(self, coro: Coroutine):
                self.coro = coro
                self.result = None

            async def _get_result(self):
                if self.result is None:
                    self.result = await self.coro
                return self.result

            def __getattr__(self, name: str):
                attr = getattr(self.result, name) if self.result else getattr(Character, name)
                if asyncio.iscoroutinefunction(attr):
                    async def wrapped(*args, **kwargs):
                        await self._get_result()
                        coro = attr(self.result, *args, **kwargs)
                        return Wrapper(coro)
                    return wrapped
                else:
                    def wrapped(*args, **kwargs):
                        self._get_result()
                        self.result = attr(self.result, *args, **kwargs)
                        return self
                    return wrapped

            def __await__(self):
                return self._get_result().__await__()

        return Wrapper(self._hasJob())

    # isKnight方法做同样改造
    async def _isKnight(self) -> Self:
        await asyncio.sleep(1)
        return self

    def isKnight(self):
        class Wrapper:
            def __init__(self, coro: Coroutine):
                self.coro = coro
                self.result = None

            async def _get_result(self):
                if self.result is None:
                    self.result = await self.coro
                return self.result

            def __getattr__(self, name: str):
                attr = getattr(self.result, name) if self.result else getattr(Character, name)
                if asyncio.iscoroutinefunction(attr):
                    async def wrapped(*args, **kwargs):
                        await self._get_result()
                        coro = attr(self.result, *args, **kwargs)
                        return Wrapper(coro)
                    return wrapped
                else:
                    def wrapped(*args, **kwargs):
                        self._get_result()
                        self.result = attr(self.result, *args, **kwargs)
                        return self
                    return wrapped

            def __await__(self):
                return self._get_result().__await__()

        return Wrapper(self._isKnight())

调用方式完全符合预期:

async def main():
    john = await (
        Character("John", human=True)
        .isHuman()
        .hasJob()
        .isKnight()
    )
    print(john.name)

asyncio.run(main())

方案对比

  • 方案一的通用包装类更灵活,可复用给其他需要异步链式调用的类;
  • 方案二更直接,无需额外依赖,但需要逐个改造异步方法。

内容的提问来源于stack exchange,提问作者ZC13

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 23:57:35