Frage

Ich habe versucht, eine Dekoratorfunktion zu schreiben, die eine umschließt asyncio.coroutine und gibt die Zeit zurück, die für die Erledigung benötigt wurde.Das Rezept unten enthält den Code, der wie erwartet funktioniert.Mein einziges Problem dabei ist, dass ich trotz der Verwendung von irgendwie den Namen der dekorierten Funktion verliere @functools.wraps.Wie kann der Name der ursprünglichen Coroutine beibehalten werden?Ich habe die Quelle überprüft 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__))

Das Ergebnis:

= 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' =

Wie du sehen kannst random_sleep() gibt ein Generatorobjekt mit einem anderen Namen zurück.Ich möchte den Namen der dekorierten Coroutine beibehalten.Mir ist nicht bekannt, ob dieses Problem spezifisch ist asyncio.coroutines oder nicht.Ich habe den Code auch mit verschiedenen Dekorationsaufträgen ausprobiert, aber alle haben das gleiche Ergebnis erzielt.Wenn ich kommentiere @functools.wraps(coro) dann sogar random_sleep.__name__ wird wrapper wie ich erwartet habe.

BEARBEITEN:Ich habe dieses Problem im Python Issue Tracker gepostet und die folgende Antwort von R erhalten.David Murray:„Ich denke, dies ist ein konkreter Fall einer allgemeineren Notwendigkeit, ‚Wraps‘ zu verbessern, die vor nicht allzu langer Zeit auf python-dev diskutiert wurde.“

War es hilfreich?

Lösung

Das Problem ist das functools.wraps nur Änderungen wrapper.__name__ Und wrapper().__name__ bleibt wrapper. __name__ ist ein schreibgeschütztes Generatorattribut.Du könntest benutzen exec So legen Sie den passenden Namen fest:

import asyncio
import functools
import uuid
from textwrap import dedent

def wrap_coroutine(coro, name_prefix='__' + uuid.uuid4().hex):
    """Like functools.wraps but preserves coroutine names."""
    # attribute __name__ is not writable for a generator, set it dynamically
    namespace = {
        # use name_prefix to avoid an accidental name conflict
        name_prefix + 'coro': coro,
        name_prefix + 'functools': functools,
        name_prefix + 'asyncio': asyncio,
    }
    exec(dedent('''
        def {0}decorator({0}wrapper_coro):
            @{0}functools.wraps({0}coro)
            @{0}asyncio.coroutine
            def {wrapper_name}(*{0}args, **{0}kwargs):
                {0}result = yield from {0}wrapper_coro(*{0}args, **{0}kwargs)
                return {0}result
            return {wrapper_name}
        ''').format(name_prefix, wrapper_name=coro.__name__), namespace)
    return namespace[name_prefix + 'decorator']

Verwendung:

def time_resulted(coro):
    @wrap_coroutine(coro)
    def wrapper(*args, **kargs):
        # ...
    return wrapper

Es funktioniert, aber es gibt wahrscheinlich einen besseren Weg als die Verwendung exec().

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top