es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Obtener un valor de retorno de un decorador con generador en Python.

Tengo un generador en Python. Digamos:

def gen(*args, **kwargs):
    yield 1
    yield 2
    return 3

Ahora, quiero lo siguiente. Creo un objeto generador gen_obj = gen().
Luego, hago algo con él, típicamente value = next(gen).
y al final quiero hacer algo así:
returned_value = gen.value # debería devolver 3

Algunas respuestas para superar este problema en particular existen, pero lo complicaré más.
De hecho, no tengo un generador simple, sino un decorador:

def MakeGeneratorFromFcn(func):
    @wraps(func)
    def wrapped_func(*args, **kwargs):
        if isgeneratorfunction(func):
            gen = func(*args, **kwargs)  # si es un generador, entonces incluir el yield
            retval = yield from gen
            return retval
        return func(*args, **kwargs) # de lo contrario, esta wrapped_func ya es un generador, devuelve el api_func

    return wrapped_func

Entonces, al final me gustaría hacer algo así:

@MakeGeneratorFromFcn
def foo():
    return 10

gen = foo()
for i in gen:
    print(i)
gen.value  # devuelve 10; eso falta

y también esto funcionaría:

@MakeGeneratorFromFcn
def foo():
    yield 1
    return 10

gen = foo()
for i in gen:
    print(i)
gen.value  # devuelve 10; esto falta

EDITO #1:
Para resolver eso, utilicé esta respuesta y con algunas modificaciones:

class ValueKeepingGenerator(object):
    def __init__(self, g):
        self.g = g
        self.value = None
    def __iter__(self):
        return self
    def __next__(self):
        self.value = next(self.g)
        return self.value

Junto con otra decoración “de función a generador”, pude hacer que el código funcionara para mis propósitos.

Tags:  , , ,

Answer

  1. Avatar for davy.ai

    Solución

    El problema principal del código dado es que se pierde el valor de retorno de la función generadora cuando se llama usando un decorador. Para resolver este problema, podemos modificar la función decoradora MakeGeneratorFromFcn para envolver la generadora en una clase que almacene el valor de retorno de la generadora.

    Una forma de lograr esto es crear una nueva clase llamada ValueKeepingGenerator que tome una generadora como entrada y almacene su valor de retorno. Podemos modificar la función MakeGeneratorFromFcn para usar esta clase y envolver la generadora.

    Así es como podemos modificar la clase ValueKeepingGenerator para almacenar el valor de retorno:

    class ValueKeepingGenerator:
        def __init__(self, gen):
            self.gen = gen
            self.returned_value = None
    
        def __iter__(self):
            return self
    
        def __next__(self):
            try:
                value = next(self.gen)
            except StopIteration as e:
                self.returned_value = e.value
                raise
            else:
                return value
    

    Podemos modificar la función MakeGeneratorFromFcn para usar esta clase y envolver la generadora:

    def MakeGeneratorFromFcn(func):
        @wraps(func)
        def wrapped_func(*args, **kwargs):
            if isgeneratorfunction(func):
                gen = func(*args, **kwargs)
                return ValueKeepingGenerator(gen)
            return func(*args, **kwargs)
    
        return wrapped_func
    

    Finalmente, podemos usar la función MakeGeneratorFromFcn modificada para decorar la función foo de la siguiente manera:

    @MakeGeneratorFromFcn
    def foo():
        yield 1
        yield 2
        return 3
    

    Ahora, podemos usar la generadora foo y acceder a su valor de retorno de la siguiente manera:

    gen = foo()
    for i in gen:
        print(i)
    print(gen.returned_value)  # imprime 3
    

    De manera similar, podemos modificar la línea gen.value en el código original a gen.returned_value.

    Esta solución funciona para generadoras que tienen un valor de retorno. Si una generadora no tiene un valor de retorno, entonces gen.returned_value será None.

Comments are closed.