Python asyncio 类作为Python中常规函数和协同例程的装饰器

Python asyncio 类作为Python中常规函数和协同例程的装饰器,python-asyncio,python-3.7,python-decorators,Python Asyncio,Python 3.7,Python Decorators,我试图创建一个类作为装饰器,它将对装饰函数应用try-except块,并保留一些异常日志。我想将decorator应用于常规函数和协同例程 我以decorator的身份完成了这个类,它的工作原理与为常规函数设计的一样,但是协同程序出现了一些问题。下面是作为装饰器的类的简化版本的一些简化代码和几个用例: import traceback import asyncio import functools class Try: def __init__(self, func):

我试图创建一个类作为装饰器,它将对装饰函数应用try-except块,并保留一些异常日志。我想将decorator应用于常规函数和协同例程

我以decorator的身份完成了这个类,它的工作原理与为常规函数设计的一样,但是协同程序出现了一些问题。下面是作为装饰器的类的简化版本的一些简化代码和几个用例:

import traceback
import asyncio
import functools

class Try:

    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f"applying __call__ to {self.func.__name__}")
        try:
            return self.func(*args, **kwargs)
        except:
            print(f"{self.func.__name__} failed")
            print(traceback.format_exc())

    def __await__(self, *args, **kwargs):
        print(f"applying __await__ to {self.func.__name__}")
        try:
            yield self.func(*args, **kwargs)
        except:
            print(f"{self.func.__name__} failed")
            print(traceback.format_exc())

# Case 1
@Try
def times2(x):
    return x*2/0

# Case 2
@Try
async def times3(x):
    await asyncio.sleep(0.0001)
    return x*3/0

async def test_try():
    return await times3(10)

def main():
    times2(10)
    asyncio.run(test_try())
    print("All done")

if __name__ == "__main__":
    main()
以下是上述代码的输出(稍加编辑):

有输出

applying __await__ to times3
times3 failed
Traceback (most recent call last):
  File "<ipython-input-5-5a85f988097e>", line 22, in __await__
    yield self.func(*args, **kwargs)
TypeError: times3() missing 1 required positional argument: 'x'
将等待应用于时间3
时间3失败
回溯(最近一次呼叫最后一次):
文件“”,第22行,在等待中__
收益率自身函数(*args,**kwargs)
TypeError:times3()缺少1个必需的位置参数:“x”

这确实会强制使用
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。通常,
wait f(x)
扩展为如下内容:

_awaitable = f(x)
_iter = _awaitable.__await__()
yield from _iter  # not literally[1]
注意如何对函数的结果而不是函数对象本身调用
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。在
时间3
示例中发生的情况如下:

async def test_try2():
    func = await times3
  • 调用
    调用
    self.func
    中原始的
    times3
    协程函数,该函数简单地构造了一个协程对象。此时没有异常,因为对象尚未开始执行,因此返回一个(通过调用
    async def
    coroutine函数得到的结果)

  • 在通过运行
    self.func
    获得的协同程序对象上调用
    \uuuuuu wait\uuuuu
    ,该对象是原始的
    times3
    async def
    ,而不是在函数包装器上。这是因为,就上面的伪代码而言,包装器对应于
    f
    ,而
    \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

一般来说,您不知道是否会等待函数调用的结果。但是,由于协同路由对象除了等待它们之外没有任何用处(而且它们甚至在没有等待的情况下被销毁时打印警告),因此您可以放心地假设是这样的。此假设允许您的
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
检查函数调用的结果是否是可等待的,如果是,则将其包装到一个对象中,该对象将在
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu wait
级别实现包装逻辑:

...
import collections.abc

class Try:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f"applying __call__ to {self.func.__name__}")
        try:
            result = self.func(*args, **kwargs)
        except:
            print(f"{self.func.__name__} failed")
            print(traceback.format_exc())
            return
        if isinstance(result, collections.abc.Awaitable):
            # The result is awaitable, wrap it in an object
            # whose __await__ will call result.__await__()
            # and catch the exceptions.
            return TryAwaitable(result)
        return result

class TryAwaitable:
    def __init__(self, awaitable):
        self.awaitable = awaitable

    def __await__(self, *args, **kwargs):
        print(f"applying __await__ to {self.awaitable.__name__}")
        try:
            return yield from self.awaitable.__await__()
        except:
            print(f"{self.awaitable.__name__} failed")
            print(traceback.format_exc())
这将产生预期的输出:

applying __call__ to times3
applying __await__ to times3
times3 failed
Traceback (most recent call last):
  File "wrap3.py", line 30, in __await__
    yield from self.awaitable.__await__()
  File "wrap3.py", line 44, in times3
    return x*3/0
ZeroDivisionError: division by zero
请注意,您的
\uuuuu wait\uuuu
实现有一个不相关的问题,它使用
yield
委托给函数。必须使用
yield from
,因为这允许基础iterable选择何时挂起,并在它停止挂起时提供一个值。裸
yield
无条件挂起(仅一次),这与
wait
的语义不兼容

一,
不是字面意思,因为在
异步def
中不允许从
产生收益。但是
async def
的行为就好像这样一个生成器是由它返回的对象的
\uuuuuu wait\uuuu
方法返回的一样。

Hey@user4815162342,感谢您的回答,它在处理异常时起作用,但是当没有异常时,没有返回值,它就没有了。有没有办法解决这个问题?@gorune这只是一个疏忽,来自
收益之前应该有一个
返回
,以实际将最终值传递给等待者。我现在已经更新了答案。好吧,我发现我必须这么做:
this=yield from self.waitible.\uuuuu wait\uuuu()返回此
,但奇怪的是,我认为yield from应该自动返回。这是正常的行为吗?编辑,谢谢,我看到了:)@gorune不,
yield from
类似于
wait
,它要么屈服于父生成器(最终是事件循环),要么在当前上下文中提供一个值。后者特定于来自
收益,并被引入以支持
等待用例。在
yield from
生成器没有“返回值”之前,
return
是一个语法错误。后来引入了
await
,并且(正确地)禁止了生成器风格的协同程序,以掩盖编写或探索低级代码的用例。例如,即使是这个例子也可以不用它来编写。
applying __call__ to times3
applying __await__ to times3
times3 failed
Traceback (most recent call last):
  File "wrap3.py", line 30, in __await__
    yield from self.awaitable.__await__()
  File "wrap3.py", line 44, in times3
    return x*3/0
ZeroDivisionError: division by zero