Python 如何创建一组可以同步和异步使用的函数?

Python 如何创建一组可以同步和异步使用的函数?,python,python-3.x,asynchronous,async-await,python-asyncio,Python,Python 3.x,Asynchronous,Async Await,Python Asyncio,假设我有一组函数,如下所示: def func1(): func2() def func2(): time.sleep(1) # simulate I/O operation print('done') # this would take 1 second to complete future = asyncio.gather(func1.run_async(), func1.run_async()) loop = asyncio.get_event_loop()

假设我有一组函数,如下所示:

def func1():
    func2()

def func2():
    time.sleep(1)  # simulate I/O operation
    print('done')
# this would take 1 second to complete
future = asyncio.gather(func1.run_async(), func1.run_async())
loop = asyncio.get_event_loop()
loop.run_until_complete(future)
我希望这些可以同步使用:

# this would take two seconds to complete
func1()
func1()
以及异步,例如:

def func1():
    func2()

def func2():
    time.sleep(1)  # simulate I/O operation
    print('done')
# this would take 1 second to complete
future = asyncio.gather(func1.run_async(), func1.run_async())
loop = asyncio.get_event_loop()
loop.run_until_complete(future)
当然,问题是
func1
必须以某种方式将其运行的“上下文”(同步或异步)传播到
func2

我希望避免编写每个函数的异步变量,因为这将导致大量重复代码:

def func1():
    func2()

def func2():
    time.sleep(1)  # simulate I/O operation
    print('done')

# duplicate code below...
async def func1_async():
    await func2_async()

async def func2_async():
    await asyncio.sleep(1)  # simulate I/O operation
    print('done')
有什么方法可以做到这一点而不必实现我所有函数的异步副本吗?

这里是我的“非答案答案”,我知道堆栈溢出喜欢

有没有什么方法可以做到这一点,而不必实现所有函数的异步副本

我认为没有。制作一个“一揽子翻译器”将函数转换为本机协同程序似乎几乎是不可能的。这是因为使同步函数异步不仅仅是在它前面抛出一个
async
关键字和其中的几个
await
语句。
请记住,任何
await
语句都必须是异步的

您的
def func2():time.sleep(1)
说明了这一点。同步函数将进行阻塞调用,例如
time.sleep()
;异步(本机协同路由)将等待非阻塞协同路由。正如您所指出的,要使此函数异步,不仅需要使用
async def func()
,还需要等待
asyncio.sleep()
。现在让我们假设您调用的不是
time.sleep()
,而是更复杂的阻塞函数。您构建了某种奇特的装饰器,它将一个名为
run\u async
(可调用)的装饰函数添加到装饰函数上。但是,如果定义了
func2()
中的阻塞调用,那么修饰符如何知道如何将这些阻塞调用“转换”为它们的协同路由等价物呢?我想不出有什么魔法能聪明到足以将同步函数中的所有调用转换为它们的
await
able对应项

在您的评论中,您提到这是针对HTTP请求的。对于一个真实的例子,
请求
aiohttp
包之间的调用签名和api的差异。在
aiohttp
中,
.text()
是一个实例;在
请求
中,
文本
是一个。你怎么能制造出足够聪明的东西来了解这样的差异呢


我并不想泄气——但我认为使用线程将更现实。

因此我找到了实现这一点的方法,但由于这是我第一次使用
async
做任何事情,我不能保证这不会有任何错误或这不是一个糟糕的想法

这个概念实际上非常简单:在必要时使用
async def
await
像普通异步函数一样定义函数,然后在它们周围添加一个包装器,如果没有运行事件循环,它会自动等待函数。概念证明:

import asyncio
import functools
import time


class Hybrid:
    def __init__(self, func):
        self._func = func

        functools.update_wrapper(self, func)

    def __call__(self, *args, **kwargs):
        coro = self._func(*args, **kwargs)

        loop = asyncio.get_event_loop()

        if loop.is_running():
            # if the loop is running, we must've been called from a
            # coroutine - so we'll return a future
            return loop.create_task(coro)
        else:
            # if the loop isn't running, we must've been called synchronously,
            # so we'll start the loop and let it execute the coroutine
            return loop.run_until_complete(coro)

    def run_async(self, *args, **kwargs):
        return self._func(*args, **kwargs)


@Hybrid
async def func1():
    await func2()

@Hybrid
async def func2():
    await asyncio.sleep(0.1)


def twice_sync():
    func1()
    func1()

def twice_async():
    future = asyncio.gather(func1.run_async(), func1.run_async())
    loop = asyncio.get_event_loop()
    loop.run_until_complete(future)


for func in [twice_sync, twice_async]:
    start = time.time()
    func()
    end = time.time()
    print('{:>11}: {} sec'.format(func.__name__, end-start))

# output:
#  twice_sync: 0.20142340660095215 sec
# twice_async: 0.10088586807250977 sec
然而,这种方法确实有其局限性。如果同步函数调用混合函数,则从异步函数调用同步函数将更改其行为:

@hybrid
async def hybrid_function():
    return "Success!"

def sync_function():
    print('hybrid returned:', hybrid_function())

async def async_function():
    sync_function()

sync_function()  # this prints "Success!" as expected

loop = asyncio.get_event_loop()
loop.run_until_complete(async_function())  # but this prints a coroutine

注意这一点

取决于函数实际在做什么,而不是为了提问而使用虚拟函数。但通常,您会调用
多处理
线程
来与其他代码并行执行调用。您可以互换使用它,这意味着您不必每次都对函数执行线程。线程/进程的基本用法。@Torxed如果重要的话,函数将执行异步HTTP请求。我可以用多线程将整个过程并行化,但我真的不愿意。与多处理和多线程相比,异步具有许多优点。我们的目标是最终得到好的代码,而不是不惜一切代价进行并行化。我不知道您的用例,但与其要求
func1()
始终使用
func2()
,不如在输入参数中给它一些逻辑来运行
func2()
(和/或默认情况下),或者如果没有提供,就使用另一个静态输入?@Torxed当我说
async
时,我指的是python的
async
关键字,当我说async(没有代码格式)时,我指的是异步执行(:@Torxed:
multiprocessing
本质上并不坏,但在某些平台上它相对来说是基本上被破坏的(例如,macOS的最新版本).
线程化本质上并不坏,但作为一种编程范例,线程很难正确执行,而且CPython无法以最佳方式处理多个线程。异步与并发不同,对于延迟敏感但受IO限制的操作,异步操作的性能可能优于单纯并发的操作,尤其是通过减少每次操作的CPU和内存开销,添加了
async
关键字是有充分理由的!自动使同步函数异步几乎是不可能的,但如果我们反过来做会怎么样?如果我用
async def
定义
func1
func2
,肯定有办法将它们转换为同步函数?(或者至少让调用者觉得它们是同步的?)我不会说这是不可能的。协同程序只是一个;也许这个属性在简单的情况下是可行的。但是,这不仅仅是语法上的差异;这是一个行为上的差异。如果它确实存在的话,我很想看到同样的东西,但我没有立即想到。很好的解释,我想离开这个库。A虽然这不是一个通用的解决方案,这个问题被作为一个关于阻塞的整体问题提出