¿Cómo implementas __str__ para una función?

Dada una función foo :

 def foo(x): pass 

Imprimir su representación invocando str o repr te da algo aburrido como esto:

 str(foo) '' 

Me gustaría saber si es posible anular el método __str__ una función para imprimir otra cosa. Esencialmente, me gustaría hacer:

 str(foo) "I'm foo!' 

Ahora, entiendo que la descripción de una función debe provenir de __doc__ que es la __doc__ de __doc__ la función. Sin embargo, esto es simplemente un experimento.

Al intentar encontrar una solución a este problema, encontré la implementación de __str__ para las classes : ¿Cómo definir un método __str__ para una clase?

Este enfoque involucró la definición de una metaclase con un método __str__ , y luego tratar de asignar el gancho __metaclass__ en la clase real.

Me pregunté si se podría hacer lo mismo con la function clase, así que esto es lo que intenté:

 In [355]: foo.__class__ Out[355]: function In [356]: class fancyfunction(type): ...: def __str__(self): ...: return self.__name__ ...: In [357]: foo.__class__.__metaclass__ = fancyfunction --------------------------------------------------------------------------- TypeError Traceback (most recent call last) 

Pensé que no funcionaría, ¡pero valió la pena intentarlo!

Entonces, ¿cuál es la mejor manera de implementar __str__ para una función?

Una función en Python es solo un objeto llamable. El uso de def para definir la función es una forma de crear dicho objeto. Pero en realidad no hay nada que le impida crear un tipo invocable y crear una instancia para obtener una función.

Así que las dos cosas siguientes son básicamente iguales:

 def foo (): print('hello world') class FooFunction: def __call__ (self): print('hello world') foo = FooFunction() 

Excepto que el último obviamente nos permite configurar los métodos especiales del tipo de función, como __str__ y __repr__ .

 class FooFunction: def __call__ (self): print('hello world') def __str__ (self): return 'Foo function' foo = FooFunction() print(foo) # Foo function 

Pero crear un tipo solo para esto se vuelve un poco tedioso y también hace que sea más difícil entender lo que hace la función: después de todo, la syntax de def nos permite simplemente definir el cuerpo de la función. ¡Así que queremos mantenerlo así!

Afortunadamente, Python tiene esta gran característica llamada decoradores que podemos usar aquí. Podemos crear un decorador de funciones que envolverá cualquier función dentro de un tipo personalizado que llame a una función personalizada para __str__ . Eso podría verse así:

 def with_str (str_func): def wrapper (f): class FuncType: def __call__ (self, *args, **kwargs): # call the original function return f(*args, **kwargs) def __str__ (self): # call the custom __str__ function return str_func() # decorate with functool.wraps to make the resulting function appear like f return functools.wraps(f)(FuncType()) return wrapper 

Luego podemos usar eso para agregar una función __str__ a cualquier función simplemente decorándola. Eso se vería así:

 def foo_str (): return 'This is the __str__ for the foo function' @with_str(foo_str) def foo (): print('hello world') 
 >>> str(foo) 'This is the __str__ for the foo function' >>> foo() hello world 

Obviamente, hacer esto tiene algunas limitaciones e inconvenientes ya que no puedes reproducir exactamente lo que def haría para una nueva función dentro de ese decorador.

Por ejemplo, usar el módulo de inspect para ver los argumentos no funcionará correctamente: para el tipo que se puede llamar, incluirá el argumento self y, cuando se use el decorador genérico, solo se podrán informar los detalles de la wrapper . Sin embargo, puede haber algunas soluciones, por ejemplo discutidas en esta pregunta , que le permitirán restaurar algunas de las funciones.

Pero eso generalmente significa que está invirtiendo mucho esfuerzo solo para conseguir un trabajo __str__ en un objeto de función que probablemente muy rara vez se usará. Por lo tanto, debe pensar si realmente necesita una implementación __str__ para sus funciones y qué tipo de operaciones realizará en esas funciones en ese momento.

Si te encuentras envolviendo funciones, es útil mirar functools.partial . Es principalmente para argumentos vinculantes, por supuesto, pero eso es opcional. También es una clase que envuelve funciones, eliminando la repetición de hacerlo desde cero.

 from functools import partial class foo(partial): def __str__(self): return "I'm foo!" @foo def foo(): pass assert foo() is None assert str(foo) == "I'm foo!"