【发布时间】:2014-05-25 18:55:00
【问题描述】:
我尝试编写一个装饰器函数来包装asyncio.coroutine 并返回完成所花费的时间。下面的配方包含按我预期工作的代码。我唯一的问题是,尽管使用了@functools.wraps,我还是以某种方式丢失了装饰函数的名称。如何保留原有协程的名称?我查了asyncio.的来源
import asyncio
import functools
import random
import time
MULTIPLIER = 5
def time_resulted(coro):
@functools.wraps(coro)
@asyncio.coroutine
def wrapper(*args, **kargs):
time_before = time.time()
result = yield from coro(*args, **kargs)
if result is not None:
raise TypeError('time resulted coroutine can '
'only return None')
return time_before, time.time()
print('= wrapper.__name__: {!r} ='.format(wrapper.__name__))
return wrapper
@time_resulted
@asyncio.coroutine
def random_sleep():
sleep_time = random.random() * MULTIPLIER
print('{} -> {}'.format(time.time(), sleep_time))
yield from asyncio.sleep(sleep_time)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
tasks = [asyncio.Task(random_sleep()) for i in range(5)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
for task in tasks:
print(task, task.result()[1] - task.result()[0])
print('= random_sleep.__name__: {!r} ='.format(
random_sleep.__name__))
print('= random_sleep().__name__: {!r} ='.format(
random_sleep().__name__))
结果:
= wrapper.__name__: 'random_sleep' =
1397226479.00875 -> 4.261069174838891
1397226479.00875 -> 0.6596335046471768
1397226479.00875 -> 3.83421163259601
1397226479.00875 -> 2.5514027672929713
1397226479.00875 -> 4.497471439365472
Task(<wrapper>)<result=(1397226479.00875, 1397226483.274884)> 4.266134023666382
Task(<wrapper>)<result=(1397226479.00875, 1397226479.6697)> 0.6609499454498291
Task(<wrapper>)<result=(1397226479.00875, 1397226482.844265)> 3.835515022277832
Task(<wrapper>)<result=(1397226479.00875, 1397226481.562422)> 2.5536720752716064
Task(<wrapper>)<result=(1397226479.00875, 1397226483.51523)> 4.506479978561401
= random_sleep.__name__: 'random_sleep' =
= random_sleep().__name__: 'wrapper' =
如您所见,random_sleep() 返回一个具有不同名称的生成器对象。我想保留装饰协程的名称。我不知道这个问题是否特定于asyncio.coroutines。我还尝试了具有不同装饰器命令的代码,但结果都相同。如果我评论@functools.wraps(coro),那么即使random_sleep.__name__ 也会像我预期的那样变成wrapper。
编辑:我已将此问题发布到 Python 问题跟踪器,并收到了 R. David Murray 的以下回答:“我认为这是一个更普遍的需要改进在 python 上讨论的“包装”的具体案例-不久前的开发者。”
【问题讨论】:
-
您可以设法将您想要的名称保存在您自己的变量中,作为一种解决方法。需要明确的是,您想要的行为是
wrapper.__name__必须是“包装器”,而不仅仅是您希望从某个地方获得该值? -
如您所见,
asyncio.Task().__repr__()包括生成器对象的名称 (random_sleep().__name__),它仍然是wrapper。虽然我用@functools.wraps装饰了包装器,但它只是将名称复制到了函数级别,但是一旦函数被任务调用,生成的生成器对象就不会继承该名称。这意味着所有包含@time_resulted协程的任务都代表自己包含一个wrapper协程,这使得理解回溯和错误检查变得更加困难。 Link to the Issue -
我认为你提出了一个很好的问题,而且看起来你会比这里得到更多的帮助。无论如何都赞成你的问题。 (谁知道呢?在 SO 上有一些非常聪明的 Python 开发人员。)
-
python 3.6 不再是这种情况了。
标签: python-3.x decorator coroutine python-3.4 python-asyncio