¿Cómo crear una representación de cadena personalizada para un objeto de clase?

202

Considera esta clase:

class foo(object):
    pass

La representación de cadena predeterminada se parece a esto:

>>> str(foo)
"<class '__main__.foo'>"

¿Cómo puedo hacer que esta pantalla sea una cadena personalizada?

Björn Pollex
fuente

Respuestas:

272

Implementar __str__()o __repr__()en la metaclase de la clase.

class MC(type):
  def __repr__(self):
    return 'Wahaha!'

class C(object):
  __metaclass__ = MC

print C

Úselo __str__si se refiere a una stringificación legible, úselo __repr__para representaciones inequívocas.

Ignacio Vazquez-Abrams
fuente
2
Me gustaría crear algún tipo de decorador de clases, para poder configurar fácilmente representaciones de cadenas personalizadas para mis clases sin tener que escribir una metaclase para cada una de ellas. No estoy muy familiarizado con las metaclases de Python, así que ¿puedes darme algunos consejos allí?
Björn Pollex
Lamentablemente, esto no se puede hacer con decoradores de clase; debe establecerse cuando se define la clase.
Ignacio Vazquez-Abrams
10
@ Space_C0wb0y: podría agregar una cadena como _representational cuerpo de la clase y return self._representationen el __repr__()método de la metaclase.
Sven Marnach
@ BjörnPollex Es posible que pueda resolver esto con decorador, pero espero que tenga que luchar con muchas sutilezas del lenguaje. E incluso si lo hace, todavía está obligado a usar metaclase de una manera u otra, ya que no desea usar el valor predeterminado __repr__para representar C. Una alternativa a tener un _representationmiembro es crear una fábrica de metaclases que produzca una metaclase con la adecuada __repr__(esto podría ser bueno si está usando esto mucho).
Skyking
2
@ThomasLeonard: stackoverflow.com/questions/39013249/metaclass-in-python3-5
Ignacio Vazquez-Abrams
22
class foo(object):
    def __str__(self):
        return "representation"
    def __unicode__(self):
        return u"representation"
Andrey Gubarev
fuente
10
Esto cambia la representación de cadena para instancesla clase, no para la clase en sí.
tauran
lo siento, no veo la segunda parte de tu publicación. Use el método anterior.
Andrey Gubarev
66
@RobertSiemer ¿Por qué? Si bien su respuesta no apunta específicamente a la pregunta del OP, sigue siendo útil. Me ayudó. Y de un vistazo, no veo ninguna pregunta sobre la implementación de la instancia. Entonces, probablemente la gente aterrice primero en esta página.
akinuri
14

Si tiene que elegir __repr__o elegir __str__el primero, como por defecto __str__llama a la implementación__repr__ cuando no se definió.

Ejemplo de Vector3 personalizado:

class Vector3(object):
    def __init__(self, args):
        self.x = args[0]
        self.y = args[1]
        self.z = args[2]

    def __repr__(self):
        return "Vector3([{0},{1},{2}])".format(self.x, self.y, self.z)

    def __str__(self):
        return "x: {0}, y: {1}, z: {2}".format(self.x, self.y, self.z)

En este ejemplo, reprdevuelve nuevamente una cadena que se puede consumir / ejecutar directamente, mientras que stres más útil como salida de depuración.

v = Vector3([1,2,3])
print repr(v)    #Vector3([1,2,3])
print str(v)     #x:1, y:2, z:3
usuario1767754
fuente
1
Si bien su punto sobre __repr__vs __str__es correcto, esto no responde a la pregunta real, que se trata de objetos de clase, no de instancias.
Björn Pollex
Gracias por los comentarios, supervisé completamente eso. Déjame revisar mi respuesta.
user1767754
Creo que sus implementaciones para repr y str se intercambian.
jspencer
4

La respuesta aprobada de Ignacio Vázquez-Abrams es bastante correcta. Sin embargo, es de la generación Python 2. Una actualización para el actual Python 3 sería:

class MC(type):
  def __repr__(self):
    return 'Wahaha!'

class C(object, metaclass=MC):
    pass

print(C)

Si desea código que se ejecute tanto en Python 2 como en Python 3, el módulo seis lo tiene cubierto:

from __future__ import print_function
from six import with_metaclass

class MC(type):
  def __repr__(self):
    return 'Wahaha!'

class C(with_metaclass(MC)):
    pass

print(C)

Finalmente, si tiene una clase en la que desea tener una reproducción estática personalizada, el enfoque basado en la clase anterior funciona muy bien. Pero si tiene varios, tendría que generar una metaclase similar a la MCde cada uno, y eso puede ser agotador. En ese caso, llevar su metaprogramación un paso más allá y crear una fábrica de metaclases hace las cosas un poco más limpias:

from __future__ import print_function
from six import with_metaclass

def custom_class_repr(name):
    """
    Factory that returns custom metaclass with a class ``__repr__`` that
    returns ``name``.
    """
    return type('whatever', (type,), {'__repr__': lambda self: name})

class C(with_metaclass(custom_class_repr('Wahaha!'))): pass

class D(with_metaclass(custom_class_repr('Booyah!'))): pass

class E(with_metaclass(custom_class_repr('Gotcha!'))): pass

print(C, D, E)

huellas dactilares:

Wahaha! Booyah! Gotcha!

La metaprogramación no es algo que generalmente necesita todos los días, pero cuando lo necesita, ¡realmente da en el clavo!

Jonathan Eunice
fuente
0

Solo agregando a todas las buenas respuestas, mi versión con decoración:

from __future__ import print_function
import six

def classrep(rep):
    def decorate(cls):
        class RepMetaclass(type):
            def __repr__(self):
                return rep

        class Decorated(six.with_metaclass(RepMetaclass, cls)):
            pass

        return Decorated
    return decorate


@classrep("Wahaha!")
class C(object):
    pass

print(C)

stdout:

Wahaha!

Los inconvenientes:

  1. No puedes declarar Csin una superclase (no class C:)
  2. Clas instancias serán instancias de alguna derivación extraña, por lo que probablemente sea una buena idea agregar también una __repr__para las instancias.
Aviv Goll
fuente