¿Cómo puedo elegir una representación de cadena personalizada para una clase en sí (no instancias de la clase)?

6 minutos de lectura

Avatar de usuario de Björn Pollex
Björn Pollex

Considere 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?


Consulte ¿Cómo imprimir instancias de una clase usando print()? para la pregunta correspondiente sobre las instancias de la clase.

De hecho, esta pregunta es realmente un caso especial de esa, porque en Python, las clases también son objetos que pertenecen a su propia clase, pero no es directamente obvio cómo aplicar el consejo, ya que la “clase de clases” predeterminada es pre -definido.

Avatar de usuario de Ignacio Vázquez-Abrams
Ignacio Vázquez-Abrams

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)

Usar __str__ si te refieres a una cadena legible, usa __repr__ para representaciones inequívocas.

Editar: Versión de Python 3

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

class C(object, metaclass=MC):
    pass


print(C)

  • Me gustaría crear algún tipo de decorador de clases, de modo que pueda 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, ¿puede darme algún consejo al respecto?

    –Björn Pollex

    8 de febrero de 2011 a las 11:50

  • Desafortunadamente, esto no se puede hacer con los decoradores de clase; debe establecerse cuando se define la clase.

    – Ignacio Vázquez-Abrams

    8 de febrero de 2011 a las 11:55


  • @Space_C0wb0y: podría agregar una cadena como _representation al cuerpo de clase y return self._representation en el __repr__() método de la metaclase.

    – Sven Marnach

    8 de febrero de 2011 a las 12:50

  • @BjörnPollex Es posible que pueda lograr esto con el 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 forma u otra, ya que no desea usar el valor predeterminado __repr__ representar C. Una alternativa a tener un _representation miembro es crear una fábrica de metaclases que produzca una metaclase con el __repr__ (Esto podría ser bueno si lo estás usando mucho).

    – Rey del cielo

    01/04/2016 a las 10:10

  • @ThomasLeonard: stackoverflow.com/questions/39013249/metaclass-in-python3-5

    – Ignacio Vázquez-Abrams

    8 de noviembre de 2017 a las 18:34

class foo(object):
    def __str__(self):
        return "representation"
    def __unicode__(self):
        return u"representation"

  • Esto cambia la representación de cadena para instances de la clase, no para la clase misma.

    – tauro

    8 de febrero de 2011 a las 11:39


  • lo siento, no veo la segunda parte de tu publicación. Utilice el método anterior.

    – Andrei Gubarev

    8 de febrero de 2011 a las 11:43

  • @RobertSiemer ¿Por qué? Si bien su respuesta no se dirige específicamente a la pregunta del OP, sigue siendo útil. Me ayudó. Y de un vistazo, no veo ninguna pregunta que solicite la implementación de instancias. Así que probablemente la gente llegue primero a esta página.

    – akinuri

    21 de mayo de 2018 a las 11:55


avatar de usuario de user1767754
usuario1767754

Si tienes que elegir entre __repr__ o __str__ ve por el primero, como por defecto implementación __str__ llamadas __repr__ cuando no estaba definido.

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, repr devuelve de nuevo una cadena que se puede consumir/ejecutar directamente, mientras que str es 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

  • Mientras que su punto sobre __repr__ contra __str__ es correcto, esto no responde la pregunta real, que se trata de objetos de clase, no de instancias.

    –Björn Pollex

    12 de enero de 2018 a las 9:39

  • Gracias por los comentarios, lo supervisé por completo. Déjame revisar mi respuesta.

    – usuario1767754

    12 de enero de 2018 a las 9:48

  • Creo que sus implementaciones para repr y str están intercambiadas.

    – jspencer

    9 de marzo de 2018 a las 0:09

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 un código que se ejecute tanto en Python 2 como en Python 3, el seis el módulo 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 que desea tener una representación estática personalizada, el enfoque basado en clases anterior funciona muy bien. Pero si tiene varios, tendría que generar una metaclase similar a MC para cada uno, y eso puede volverse tedioso. En ese caso, llevar su metaprogramación un paso más allá y crear una fábrica de metaclases hace que las cosas sean 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 necesite todos los días, pero cuando lo necesita, ¡realmente da en el clavo!

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)

salida estándar:

Wahaha!

Los lados negativos:

  1. no puedes declarar C sin superclase (no class C:)
  2. C instancias serán instancias de alguna derivación extraña, por lo que probablemente sea una buena idea agregar un __repr__ para las instancias también.

Avatar de usuario de Rebs
rebeldes

Debido a que necesita una metaclase para hacer esto, pero necesita que la metaclase en sí tenga un parámetro, puede hacerlo con una metaclase que capture el nombre a través del alcance léxico.

Encuentro esto un poco más fácil de leer/seguir que algunas de las alternativas.


class type_: pass

def create_type(name):
    # we do this so that we can print the class type out
    # otherwise we must instantiate it to get a proper print out
    class type_metaclass(type):
        def __repr__(self):
            return f'<{name}>'

    class actual_type(type_, metaclass=type_metaclass):
        pass
    return actual_type

my_type = create_type('my_type')

print(my_type)
# prints "<my_type>"

Avatar de usuario de David Gilbertson
david gilbertson

Otra respuesta, con:

  • decorador
  • tipos (por lo que mantiene autocompletar en IDE)
  • funciona a partir de v3.10
import typing


class ClassReprMeta(type):
    def __repr__(self):
        attrs_str = ", ".join(
            f"{key}={getattr(self, key)}"
            for key in dir(self)
            if not key.startswith("_")
        )

        return f"{self.__name__}({attrs_str})"


T = typing.TypeVar("T")


def printable_class(cls: T) -> T:
    """Decorator to make a class object printable"""
    return ClassReprMeta(cls.__name__, cls.__bases__, dict(cls.__dict__))


@printable_class
class CONFIG:
    FIRST = 1
    SECOND = 2


print(CONFIG)  # CONFIG(FIRST=1, SECOND=2)

¿Ha sido útil esta solución?