Python asyncio 类作为Python中常规函数和协同例程的装饰器
我试图创建一个类作为装饰器,它将对装饰函数应用try-except块,并保留一些异常日志。我想将decorator应用于常规函数和协同例程 我以decorator的身份完成了这个类,它的工作原理与为常规函数设计的一样,但是协同程序出现了一些问题。下面是作为装饰器的类的简化版本的一些简化代码和几个用例: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):
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