Domanda

Ho provato a scrivere una funzione di decoratore che avvolge un asyncio.coroutine e restituisce il tempo necessario per ottenere fatto.La ricetta qui sotto contiene il codice che funziona come mi aspettavo.Il mio unico problema con esso che in qualche modo perdo il nome della funzione decorata nonostante l'uso di @functools.wraps.Come mantenere il nome della coroutine originale?Ho controllato la fonte di 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__))

Risultato:

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

Come puoi vedere random_sleep() restituisce un oggetto generatore con nome diverso.Vorrei mantenere il nome della coroutine decorata.Non sono a conoscenza se questo è un problema specifico per asyncio.coroutines oppure no.Ho anche provato il codice con diversi ordini di decoratori, ma tutti hanno lo stesso risultato.Se commento @functools.wraps(coro) poi anche random_sleep.__name__ diventare wrapper come mi aspettavo.

MODIFICARE:Ho pubblicato questo problema su Python Issue Tracker e ho ricevuto la seguente risposta da R.David Murray:"Penso che questo sia un caso specifico di una necessità più generale di migliorare gli 'involucri' che è stato discusso su python-dev non molto tempo fa."

È stato utile?

Soluzione

Il problema è che functools.wraps solo modifiche wrapper.__name__ e wrapper().__name__ soggiorno wrapper. __name__ è un attributo generatore di sola lettura.Potresti usare exec per impostare il nome appropriato:

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

Utilizzo:

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

Funziona ma probabilmente c'è un modo migliore che usare exec().

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top