PyWombat

← Volver a artículos

str & repr en Python

September 29, 2024

87 views

4 min de lectura

En Python, puntualmente cuando nos encontramos trabajando con programación orientada de objetos, tenemos 2 métodos muy importantes al momento de crear nuestras clases. Me refiero a los métodos str y repr, métodos que nos permiten customizar nuestros objetos.

Conocer como funcionan y donde utilizarlos dichos métodos es de suma importancia. Es por que ello que, en esta nueva entrada me gustaría hablemos acerca de ellos. Aprenderemos exactamente que son, cómo funcionan y cuando es, o no, una buena idea usarlos.

Dunder Methods

Cómo sabemos, y si no ahora ya lo sabes, en Python, todas nuestras clases heredan de la clase object, clase que posee sus propios atributos y métodos. Dentro de estos métodos encontraremos a los ya conocimos dunder methods, métodos que podemos reconocer fácilmente ya que inician y finalizan con doble guión bajo ( _ _ )

Estos métodos se caracterizan por poder definir comportamientos al momento de trabajar con objetos. Quizás el más conocido es el método _ init _, método que nos permite inicializar atributos para nuestros objetos.

Pues bien, 2 de esos dunder methods son los métodos str & repr. Ambos métodos retornan un string y nos permiten representar un objeto. Si bien ambos métodos hacen prácticamente los mismo, la diferencia radica en el donde utilizarlos.

Veamos un par de ejemplos. Creemos una clase User.

class User:
    def __init__(self, username, age) -> None:
        self.username = username
        self.age = age


user1 = User('john', 18)
user2 = User('jane', 20)

print(user1)
print(user2)

Si instanciamos un par de objetos y los imprimos en consola, la salida debería verse algo como lo siguiente:

<__main__.User object at 0x1047944c0>
<__main__.User object at 0x1047c1fd0>

Una salida que no nos dice mucho acerca de los objetos, más allá que ambos son objeto de tipo User.

Si quisiéramos conocer de que objeto (usuario) se trata cada impresión (John or Jane) no podríamos saberlo con la información que nos ofrece la consola.

Para poder solucionar esto, el método str llega para salvar el día.

El método str permite representar de manera amigable a nuestros objetos.

class User:
    def __init__(self, username, age) -> None:
        self.username = username
        self.age = age

    def __str__(self) -> str:
        return f"User {self.username} with age {self.age}"

user1 = User('john', 30)
user2 = User('jane', 31)

print(user1)
print(user2)

Ahora la salida luce mucho mejor.

User john with age 30
User jane with age 31

Claro, ahora estamos trabajando con una clase mucho más sencilla, pero podemos complicarla un poco más.

E.g

class Country:
    def __init__(self, name, code, emoji) -> None:
        self.name = name
        self.code = code
        self.emoji = emoji

    def __str__(self) -> str:
        return f"Country: {self.name} - {self.code} - {self.emoji}"

mexico  = Country("Mexico", "MX", "🇲🇽")
print(mexico)

>>> Country: Mexico - MX - 🇲🇽

Cómo puedes notar, ahora todas las impresiones en consola son mucho más amigables para el usuario final, pudiendo así reconocer con que objeto se esta trabajando.

El método str tiene como finalidad representar el objeto como un string, es por ello que siempre que usemos el método print obtendremos los que dicho método retorne.

De igual forma, si queremos generar un nuevo string a partir de nuestro objeto, con el método str podemos hacerlo.

mexico  = Country("Mexico", "MX", "🇲🇽")
mexico_str = str(mexico)
print(mexico_str)

>>> Country: Mexico - MX - 🇲🇽

Para este segundo ejemplo hacemos el llamado de la función str, que a su vez hace el llamado del método str para nuestro objeto.

Genial, no lo crees?

Ahora hablemos del método repr. Este método es un poco más especial, ya que a diferencia de str donde obtendremos un string amigable que permite conocer más en detalle al objeto per se, con repr trataremos de ser lo más ambiguos posible.

Mientras que str esta pensando para el proceso de desarrollo y el usuario final, repr se enfoca en el debugging.

Veamos un ejemplo.

class User:
    def __init__(self, username, age) -> None:
        self.username = username
        self.age = age

    def __str__(self) -> str:
        return f"User {self.username} with age {self.age}"

    def __repr__(self) -> str:
        return f"User(name={self.username!r}, age={self.age!r})"


user = User("jdoe", 31)
print(repr(user)) 

>>> User(name="jdoe", age=31)

El método rep retorna un string. Se espera que este string sea una expresión valida que permita recrear el objeto. De nuevo, esto con findes de debug.

Para este nuevo ejemplo hay 2 cosas importantes a destacar.

1.- La función print siempre usará el método str para poder mostrar el objeto en consola, es por ello que debemos apoyarnos de la función repr para poder acceder a la representación del objeto.

2.- Haciendo uso de un f-string podemos usar el sub-fijo !r para indicar a python que deseamos imprimir la representación del objeto.

El segundo punto es muy importante, por eso veamos un par de ejemplos más.

user = User('jdoe', 31)

print(f'{user}') 
print(f'{user!r}') 

>>> User jdoe with age 31
>>> Person(name='"doe", age=31)

Como puede observar la usar f-string por default hará uso del método str para poder mostrar el objeto. Sin embargo usando el prefijo !r le indicamos a Python que haremos uso del método repr.

Esta es la razón por la cual nuestro método repr, de la clase User, usa !r para los atributos de cada objeto (Username & Correo).

__ repr __ Como método principal?

Algo interesante de estos métodos es que, como pudimos apreciar, no son mutuamente excluyentes, podemos tener ambos en nuestras clases, que de hecho lo recomiendo, sin embargo realmente solo podemos usar __ repr , ya que __ str hace el llamado a __ repr __.

Aquí otro ejemplo.

class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return f"User(name={self.name!r}, age={self.age!r})"

people = [
    User("Alice", 30),
    User("Bob", 25),
    User("Charlie", 35)
]

print(people)

Salida:

[
    User(name="Alice", age=30), 
    User(name="Bob", age=25), 
    User(name="Charlie", age=35)
]

Como puede observar, al imprimir los objetos en consola se hace el llamado a repr. Dejando a un lado str. Esto se debe a que, el comportamiento por default de str es hacer el llamado a repr.

class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return f"User(name={self.name!r}, age={self.age!r})"

user = User("Alice", 30)
print(user.__str__())

Conclusión

Como letras finales recordar que, tanto el método str como repr son dunder methods en Python. Sin bien ambos tienen como finalidad la representación de objetos, debemos conocer que, el método str lo usaremos cuando queremos representar de forma amigable a nuestros objetos, esto habitualmente en desarrollo. Por otra parte hacemos uso de repr para poder conocer y re crear nuestro objeto, información que será de mucha utilidad en procesos de debugging.