repr y str de Python


En Python es importante comprender claramente la diferencia entre __repr__ vs __str__, lo que nos brindará el poder utilizarlos siempre que creemos cualquier clase personalizada.

Código en GitHub (Recomiendo primero leer el artículo y descargar/clonar el código al final):
https://github.com/Invarato/JarrobaPython/tree/master/repr_y_str

Para poder entenderlo con ejemplos, vamos a utilizar una clase personalizada de Python muy sencilla en la que guardaremos deportes, inicializaremos en el __init__ un listado de deportes (self.listado_deportes) y tendremos un método para añadir deporte a deporte a la clase (add_deporte):

class MisDeportes(object):

    def __init__(self):
        self.listado_deportes = []

    def add_deporte(self, deporte: str):
        self.listado_deportes.append(deporte)

Instanciar y añadir deportes a esta clase es muy fácil:

if __name__ == "__main__":
    mis_deportes = MisDeportes()

    mis_deportes.add_deporte('Running')
    mis_deportes.add_deporte('Futbol')
    mis_deportes.add_deporte('Ciclismo')

Y ahora queremos ver lo que tiene dentro el objeto de nuestra clase personalizada, por lo que optamos por imprimir esta clase:

print(mis_deportes)

Nos pintará por consola algo como esto:

<__main__.MisDeportes object at 0x00000195FE3F4F98>

No es lo que queríamos pues queríamos ver los deportes que habíamos añadido al objeto de nuestra clase personalizada.

Nos ha impreso el nombre de la clase al que pertenece el objeto y la posición de memoria en hexadecimal ¿Y de dónde saca esto Python? Si no se implementa el método mágico __str__ llama al otro método mágico __repr__; y si no se implementa tampoco __repr__ crea por defecto este String entre los símbolos mayor que y menor que: “< descripción del objeto >”.


__str__

El objetivo del String devuelto es que lo lea un humano. Devuelve un String con una representación legible (fácil de leer) para un humano. Es una representación informal del objeto en formato String.

El String que devuelve __str__ tiene que ser bonito para que lo lean los humanos, no tiene que ser pensado para que lo ejecute o interprete ninguna máquina.

Tenemos que ampliar nuestra clase personalizada anterior con este método. Yo quiero que se muestre un texto de cabecera “Objeto de deportes”, y línea a línea cada deporte con una viñeta delante que será un asterisco (*). Sobrescribimos el método mágico __str__ con el comportamiento que queríamos:

class MisDeportes(object):

    # …

    def __str__(self) -> str:
        str_con_el_resultado = 'Objeto de deportes: '
        for deporte in self.listado_deportes:
            str_con_el_resultado += "\n  * {}".format(deporte)
        return str_con_el_resultado

Nuestra clase completa queda:

class MisDeportes(object):

    def __init__(self):
        self.listado_deportes = []

    def add_deporte(self, deporte: str):
        self.listado_deportes.append(deporte)

    def __str__(self) -> str:
        str_con_el_resultado = 'Objeto de deportes: '
        for deporte in self.listado_deportes:
            str_con_el_resultado += "\n  * {}".format(deporte)
        return str_con_el_resultado

Probamos a imprimir otra vez el objeto de nuestra clase:

if __name__ == "__main__":
    mis_deportes = MisDeportes()

    mis_deportes.add_deporte('Running')
    mis_deportes.add_deporte('Futbol')
    mis_deportes.add_deporte('Ciclismo')

    print(mis_deportes)

Y efectivamente muestra el contenido de nuestro objeto personalizado como queríamos verlo de bonito por consola:

Objeto de deportes: 
  * Running
  * Futbol
  * Ciclismo

Aunque hemos utilizado el resumen de Python para imprimir nuestro objeto (“print(mis_deportes)”), lo que realmente ha hecho Python es pasar nuestro objeto la función “str()” y éste llama al método __str__ de nuestro objeto. Es decir, hace lo siguiente:

representacion_para_humanos = str(mis_deportes)
print(representacion_para_humanos)

E imprime lo mismo:

Objeto de deportes: 
  * Running
  * Futbol
  * Ciclismo

__repr__

El objetivo del String devuelto es que lo lea una máquina con Python instalado (Ver la “Nota” del siguiente párrafo). Devuelve un String con una representación del estado del objeto y dicho String debe ser interpretado sin errores por Python. Es decir, cuando se llama a __repr__ crea una expresión de Python que para poder volver a recrear el objeto en el mismo estado (con los mismos valores) a cuando se llamó a __repr__. Es una representación formal del objeto en formato String.

Nota: Realmente “no lo tiene que leer Python” como tal, sino que es la representación oficial del objeto en formato String y sirve para un programador poder depurar sin que haya ambigüedades (al contrario que __str__ que podría presentar ambigüedades; por ejemplo, si pintamos con __str__ para que quede bonito un “set” y un “list” podrían verse iguales y confundirse, pero nunca con __repr__); la gracia de que lo pueda “interpretar una máquina con Python” es que sirve para que no tenga ambigüedad (pues es imposible escribir código ambiguo por el compilador), y que para depurar copiando y pegando la representación podamos ver qué es lo que hace para poder depurar nuestro programa de una manera sencilla (más adelante hay ejemplos de esto).

Un ejemplo del uso del String devuelto por __repr__ es que este String se podría copiar y pegar en una consola de Python para que reconstruya el objeto desde ese mismo String (y después se podría reutilizar dicho objeto).

Otro ejemplo es que el String que devuelve __repr__ tiene que poder interpretarlo la función “eval” (esta función evalúa el String pasado como parámetro como si fuera código, en este caso código Python; más información sobre la función “eval()” en https://es.wikipedia.org/wiki/Eval ).

Para completar nuestra clase personalizada sobrescribiremos el método __repr__. Queremos que devuelva un String que pueda utilizar Python para instanciar nuestro objeto. Para ello nos ayudaremos del método formateador de Strings “format()” al que se le puede pasar el “self” y que se encargue de generar un String automáticamente algunos valores:

  • {self.__class__.__name__}: Añade automáticamente el nombre de nuestra clase al String que está creando
  • {self.listado_deportes}: Añade automáticamente el listado con los valores.
class MisDeportes(object):

    # …

    def __repr__(self) -> str:
        representacion_interpretable = '{self.__class__.__name__}({self.listado_deportes})'.format(self=self)
        return representacion_interpretable

Para ver que estamos creando podemos utilizar el método “repr()” que es el que se encarga de llamar a __repr__ (de manera análoga a “str()” llamaba a __str__). He imprimimos ese String generado por __repr__ con “print()” (no nos confundamos con la impresión anterior del objeto “print(mis_deportes)”; aquí imprimimos un String generado por “repr(mis_deportes)” ):

Nota: Un poco más adelante te muestro el código de «MisDeportes» completo, antes necesitamos entender lo siguiente.

if __name__ == "__main__":
    mis_deportes = MisDeportes()

    mis_deportes.add_deporte('Running')
    mis_deportes.add_deporte('Futbol')
    mis_deportes.add_deporte('Ciclismo')

    representacion_para_maquinas = repr(mis_deportes)
    print(representacion_para_maquinas)

Que imprime:

MisDeportes(['Running', 'Futbol', 'Ciclismo'])

Si te fijas este String que se ha creado es la clase a la que se le pasa como argumento el listado de deportes que tenía dentro, que es la manera de representarlo como String y código Python. Esta clase está pidiendo que en el __init__ se amplíe con este argumento.

class MisDeportes(object):

    def __init__(self, listado_deportes=None):
        if listado_deportes is None:
            self.listado_deportes = []
        else:
            self.listado_deportes = listado_deportes

    # …

Esto quiere decir que siempre que utilicemos “repr()” necesitamos que los argumentos de __init__ estén acorde a la representación que hayamos creado y que estos argumentos reconstruyan al objeto Python al mismo estado que cuando se creó el String con __repr__.

Nuestra clase completa quedaría del siguiente modo:

class MisDeportes(object):

    def __init__(self, listado_deportes=None):
        if listado_deportes is None:
            self.listado_deportes = []
        else:
            self.listado_deportes = listado_deportes

    def add_deporte(self, deporte: str):
        self.listado_deportes.append(deporte)

    def __str__(self) -> str:
        str_con_el_resultado = 'Objeto de deportes: '
        for deporte in self.listado_deportes:
            str_con_el_resultado += "\n  * {}".format(deporte)
        return str_con_el_resultado

    def __repr__(self) -> str:
        representacion_interpretable = '{self.__class__.__name__}({self.listado_deportes})'.format(self=self)
        return representacion_interpretable

Probar el valor devuelto por __repr__ y utilizarlo para depurar

Vamos a probar que el String generado lo interprete correctamente Python. Para ello vamos a utilizar el método “eval()”, y al objeto que cree «eval()» le añadiremos un nuevo deporte (con el método «add_deporte()») para comprobar si sigue comportándose como un objeto de nuestra clase personalizada «MisDeportes»:

representacion_evaluada = eval("MisDeportes(['Running', 'Futbol', 'Ciclismo'])")
representacion_evaluada.add_deporte('Baloncesto')

Ahora vamos a ver que ha funcionado imprimiendo el contenido del objeto (esta vez llamando a __str__ para que sea más sencillo de comprobar):

print(representacion_evaluada)

Y efectivamente imprime:

Objeto de deportes: 
  * Running
  * Futbol
  * Ciclismo
  * Baloncesto

Probémoslo también por consola (aunque es casi lo mismo). Guarda la clase “MisDeportes” en un fichero que yo llamaré por ejemplo “ClaseMisDeportes.py” y guardaré en el “Escritorio”.

Abrimos una consola. Da igual con qué sistema operativo trabajes, en todos funciona igual, yo voy a abrir un PowerShell de Windows (también puedes abrir un CMD en Windows, funciona igual).

Vamos al directorio donde está nuestro fichero Python “ClaseMisDeportes.py” (en mi caso en el “Escritorio”) y ejecutamos el intérprete de Python:

> cd .\Desktop\
> python

Y probemos lo que ha generado __repr__ como antes.

Importamos nuestra clase “MisDeportes” desde el fichero Python “ClaseMisDeportes”:

>>> from ClaseMisDeportes import MisDeportes

Evaluamos (con el método “eval()”) el String generado por __repr__  que fue: «MisDeportes([‘Running’, ‘Futbol’, ‘Ciclismo’])».

>>> repr_eval = eval("MisDeportes(['Running', 'Futbol', 'Ciclismo'])")

Probemos a ver si se comporta como nuestro objeto añadiendo un nuevo deporte “Baloncesto” con el método que creamos “add_deporte()”:

>>> repr_eval.add_deporte("Baloncesto")

He imprimamos con __str__ para ver si realmente funciona:

>>> print(repr_eval)

Y vemos que efectivamente que funciona:


Bibliografía

Comparte esta entrada en:
Safe Creative #1401310112503
repr y str de Python por "www.jarroba.com" esta bajo una licencia Creative Commons
Reconocimiento-NoComercial-CompartirIgual 3.0 Unported License.
Creado a partir de la obra en www.jarroba.com

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies

ACEPTAR
Aviso de cookies