Cambiando el __name__ de un generador

Dada la siguiente configuración:

def mapper(f): def wrapper(items): for x in items: yield f(x) wrapper.__name__ = f.__name__ # This has no effect! return wrapper @mapper def double(x): return x * 2 

El decorador trabaja como se espera:

 >>> [x for x in double([1,2,3])] [2, 4, 6] 

Sin embargo, su __name__ no es el double lo deseado:

 >>> double([1,2]).__name__ "wrapper" 

¿Es posible forzar el nombre del generador? Alternativamente, ¿es posible excavar en el objeto generador y recuperar el nombre double ?

Solo copió el nombre a la función. El objeto generador producido todavía usa el nombre antiguo establecido en el momento de la comstackción.

Tendría que establecer ese nombre cada vez que se produce un nuevo objeto generador; desafortunadamente, el atributo es de solo lectura para los generadores:

 >>> def gen(): ... yield None ... >>> gen().__name__ 'gen' >>> gen().__name__ = 'foo' Traceback (most recent call last): File "", line 1, in  AttributeError: attribute '__name__' of 'generator' objects is not writable 

El nombre está cocido en el objeto de código:

 >>> gen.__code__.co_name 'gen' 

por lo tanto, la única forma en que puede cambiar esto es reconstruir el objeto de código (y por extensión, la función):

 def rename_code_object(func, new_name): code_object = func.__code__ function, code = type(func), type(code_object) return function( code( code_object.co_argcount, code_object.co_nlocals, code_object.co_stacksize, code_object.co_flags, code_object.co_code, code_object.co_consts, code_object.co_names, code_object.co_varnames, code_object.co_filename, new_name, code_object.co_firstlineno, code_object.co_lnotab, code_object.co_freevars, code_object.co_cellvars), func.__globals__, new_name, func.__defaults__, func.__closure__) 

Esto crea una nueva función y un nuevo objeto de código, usando new_name para reemplazar el nombre antiguo:

 >>> new_name = rename_code_object(gen, 'new_name') >>> new_name.__name__ 'new_name' >>> new_name().__name__ 'new_name' >>> new_name()  

Usando esto en tu decorador:

 def mapper(f): def wrapper(items): for x in items: yield f(x) return rename_code_object(wrapper, f.__name__) 

Manifestación:

 >>> def mapper(f): ... def wrapper(items): ... for x in items: ... yield f(x) ... return rename_code_object(wrapper, f.__name__) ... >>> @mapper ... def double(x): ... return x * 2 ... >>> double([1, 2])  >>> list(double([1, 2])) [2, 4] 

A partir de Python 3.5, los objetos generadores toman su __name__ del objeto de función en lugar de su atributo co_name objeto de código, vea el problema # 21205 ; El atributo se convirtió en escribible en el proceso.