Buscar..


Observaciones

Una nota sobre la implementación de ambos métodos.

Cuando se implementan ambos métodos, es algo común tener un método __str__ que devuelve una representación amigable para el ser humano (por ejemplo, "As of Spaces") y __repr__ devuelve una representación amigable de eval .

De hecho, la documentación de Python para repr() nota exactamente esto:

Para muchos tipos, esta función intenta devolver una cadena que produciría un objeto con el mismo valor cuando se pasa a eval (), de lo contrario, la representación es una cadena entre paréntesis angulares que contiene el nombre del tipo de objeto. con información adicional que a menudo incluye el nombre y la dirección del objeto.

Lo que eso significa es que __str__ podría implementarse para devolver algo como "As of Spaces" como se mostró anteriormente, __repr__ podría implementarse para devolver una Card('Spades', 1)

Esta cadena podría pasarse directamente a eval en una especie de "ida y vuelta":

object -> string -> object

Un ejemplo de una implementación de tal método podría ser:

def __repr__(self):
    return "Card(%s, %d)" % (self.suit, self.pips)

Notas

[1] Esta salida es específica de la implementación. La cadena mostrada es de cpython.

[2] Es posible que ya hayas visto el resultado de esta división str() / repr() y no lo hayas conocido. Cuando las cadenas que contienen caracteres especiales, como las barras invertidas, se convierten en cadenas a través de str() las barras diagonales aparecen como están (aparecen una vez). Cuando se convierten en cadenas mediante repr() (por ejemplo, como elementos de una lista que se muestra), las barras invertidas se escapan y, por lo tanto, aparecen dos veces.

Motivación

Así que acabas de crear tu primera clase en Python, una clase pequeña y ordenada que encapsula una carta de juego:

class Card:
    def __init__(self, suit, pips):
        self.suit = suit
        self.pips = pips

En otra parte de tu código, creas algunas instancias de esta clase:

ace_of_spades = Card('Spades', 1)
four_of_clubs = Card('Clubs',  4)
six_of_hearts = Card('Hearts', 6)

Incluso has creado una lista de cartas para representar una "mano":

my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]

Ahora, durante la depuración, quieres ver cómo se ve tu mano, así que haces lo que es natural y escribes:

print(my_hand)

Pero lo que obtienes es un montón de galimatías:

[<__main__.Card instance at 0x0000000002533788>, 
 <__main__.Card instance at 0x00000000025B95C8>, 
 <__main__.Card instance at 0x00000000025FF508>]

Confundido, intenta simplemente imprimir una sola tarjeta:

print(ace_of_spades)

Y de nuevo, obtienes esta salida extraña:

<__main__.Card instance at 0x0000000002533788>

No tener miedo. Estamos a punto de arreglar esto.

Primero, sin embargo, es importante entender lo que está pasando aquí. Cuando escribió print(ace_of_spades) le dijo a Python que quería que imprimiera información sobre la instancia de la Card su código está llamando ace_of_spades . Y para ser justos, lo hizo.

Esa salida se compone de dos bits importantes: el type de objeto y la id del objeto. La segunda parte sola (el número hexadecimal) es suficiente para identificar de forma única el objeto en el momento de la llamada de print . [1]

Lo que realmente sucedió fue que le pediste a Python que "expresara con palabras" la esencia de ese objeto y luego te lo mostrara. Una versión más explícita de la misma maquinaria podría ser:

string_of_card = str(ace_of_spades)
print(string_of_card)

En la primera línea, intenta convertir la instancia de su Card en una cadena, y en la segunda, la muestra.

El problema

El problema con el que se encuentra surge debido al hecho de que, mientras le contaba a Python todo lo que necesitaba saber sobre la clase de la Card para que usted creara las tarjetas, no le dijo cómo quería que las instancias de la Card se convirtieran en cadenas.

Y como no lo sabía, cuando usted (implícitamente) escribió str(ace_of_spades) , le dio lo que vio, una representación genérica de la instancia de la Card .

La Solución (Parte 1)

Pero podemos decirle a Python cómo queremos que las instancias de nuestras clases personalizadas se conviertan en cadenas. Y la forma en que lo hacemos es con el método __str__ "dunder" (para subrayado doble) o "magic".

Siempre que le digas a Python que cree una cadena desde una instancia de clase, buscará un método __str__ en la clase y lo llamará.

Considere la siguiente versión actualizada de nuestra clase de Card :

class Card:
    def __init__(self, suit, pips):
        self.suit = suit
        self.pips = pips

    def __str__(self):
        special_names = {1:'Ace', 11:'Jack', 12:'Queen', 13:'King'}

        card_name = special_names.get(self.pips, str(self.pips))

        return "%s of %s" % (card_name, self.suit)

Aquí, ahora hemos definido el método __str__ en nuestra clase de Card que, después de una simple búsqueda en el diccionario de tarjetas de caras, devuelve una cadena con el formato que decidamos.

(Tenga en cuenta que las "devoluciones" están en negrita aquí, para resaltar la importancia de devolver una cadena y no simplemente de imprimirla. La impresión puede parecer que funciona, pero luego se imprimirá la tarjeta cuando hizo algo como str(ace_of_spades) , sin siquiera tener una llamada a la función de impresión en su programa principal. Para que quede claro, asegúrese de que __str__ devuelva una cadena.

El método __str__ es un método, por lo que el primer argumento será self y no debería aceptar, ni pasar argumentos adicionales.

Volviendo a nuestro problema de mostrar la tarjeta de una manera más fácil para el usuario, si volvemos a ejecutar:

ace_of_spades = Card('Spades', 1)
print(ace_of_spades)

Veremos que nuestra salida es mucho mejor:

Ace of Spades

Muy bien, hemos terminado, ¿verdad?

Bueno, solo para cubrir nuestras bases, verifiquemos que hemos resuelto el primer problema que encontramos, imprimiendo la lista de instancias de la Card , la hand .

Así que volvemos a comprobar el siguiente código:

my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]
print(my_hand)

Y, para nuestra sorpresa, volvemos a obtener esos divertidos códigos hex:

[<__main__.Card instance at 0x00000000026F95C8>, 
 <__main__.Card instance at 0x000000000273F4C8>, 
 <__main__.Card instance at 0x0000000002732E08>]

¿Que esta pasando? Le dijimos a Python cómo queríamos que se mostraran nuestras instancias de la Card , ¿por qué parece que aparentemente se olvidó?

La Solución (Parte 2)

Bueno, la maquinaria detrás de escena es un poco diferente cuando Python quiere obtener la representación de cadena de los elementos en una lista. Resulta que a Python no le importa __str__ para este propósito.

En su lugar, busca un método diferente, __repr__ , y si eso no es encontrado, se recurre a la "cosa hexadecimal". [2]

Entonces, ¿estás diciendo que tengo que hacer dos métodos para hacer lo mismo? ¿Uno para cuando quiero print mi tarjeta por sí mismo y otro cuando está en algún tipo de contenedor?

No, pero primero veamos cómo sería nuestra clase si implementáramos los métodos __str__ y __repr__ :

class Card:
    special_names = {1:'Ace', 11:'Jack', 12:'Queen', 13:'King'}

    def __init__(self, suit, pips):
        self.suit = suit
        self.pips = pips

    def __str__(self):
        card_name = Card.special_names.get(self.pips, str(self.pips))
        return "%s of %s (S)" % (card_name, self.suit)

    def __repr__(self):
        card_name = Card.special_names.get(self.pips, str(self.pips))
        return "%s of %s (R)" % (card_name, self.suit)

Aquí, la implementación de los dos métodos __str__ y __repr__ son exactamente iguales, excepto que, para diferenciar los dos métodos, se agrega (S) a las cadenas devueltas por __str__ y (R) se agrega a las cadenas devueltas por __repr__ .

Tenga en cuenta que al igual que nuestro método __str__ , __repr__ no acepta argumentos y devuelve una cadena.

Podemos ver ahora qué método es responsable de cada caso:

ace_of_spades = Card('Spades', 1)
four_of_clubs = Card('Clubs',  4)
six_of_hearts = Card('Hearts', 6)

my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]

print(my_hand)           # [Ace of Spades (R), 4 of Clubs (R), 6 of Hearts (R)]

print(ace_of_spades)     # Ace of Spades (S)

Como se cubrió, se llamó al método __str__ cuando pasamos nuestra instancia de Card a print y al método __repr__ cuando pasamos una lista de nuestras instancias a print .

En este punto, vale la pena señalar que al igual que podemos crear explícitamente una cadena desde una instancia de clase personalizada usando str() como lo hicimos anteriormente, también podemos crear explícitamente una representación de cadena de nuestra clase con una función incorporada llamada repr() .

Por ejemplo:

str_card = str(four_of_clubs)
print(str_card)                     # 4 of Clubs (S)

repr_card = repr(four_of_clubs)
print(repr_card)                    # 4 of Clubs (R)

Y además, si está definido, podríamos llamar a los métodos directamente (aunque parece un poco incierto e innecesario):

print(four_of_clubs.__str__())     # 4 of Clubs (S)

print(four_of_clubs.__repr__())    # 4 of Clubs (R)

Sobre esas funciones duplicadas ...

Los desarrolladores de Python se dieron cuenta de que, en el caso de que quisieras que se devolvieran cadenas idénticas desde str() y repr() es posible que tengas que duplicar funcionalmente los métodos, algo que a nadie le gusta.

Entonces, en cambio, existe un mecanismo para eliminar la necesidad de eso. Una que te colgué hasta este punto. Resulta que si una clase implementa el método __repr__ pero no el método __str__ , y usted pasa una instancia de esa clase a str() (ya sea de manera implícita o explícita), Python __repr__ su implementación __repr__ y lo usará.

Entonces, para ser claros, considere la siguiente versión de la clase de Card :

class Card:
    special_names = {1:'Ace', 11:'Jack', 12:'Queen', 13:'King'}

    def __init__(self, suit, pips):
        self.suit = suit
        self.pips = pips

    def __repr__(self):
        card_name = Card.special_names.get(self.pips, str(self.pips))
        return "%s of %s" % (card_name, self.suit)

Tenga en cuenta que esta versión solo implementa el método __repr__ . No obstante, las llamadas a str() dan como resultado una versión fácil de usar:

print(six_of_hearts)            # 6 of Hearts  (implicit conversion)
print(str(six_of_hearts))       # 6 of Hearts  (explicit conversion)

al igual que las llamadas a repr() :

print([six_of_hearts])          #[6 of Hearts] (implicit conversion)
print(repr(six_of_hearts))      # 6 of Hearts  (explicit conversion)

Resumen

Para que pueda habilitar las instancias de su clase para que se "muestren" de manera fácil de usar, querrá considerar implementar al menos el método __repr__ su clase. Si la memoria sirve, durante una charla, Raymond Hettinger dijo que asegurarse de que las clases implementen __repr__ es una de las primeras cosas que busca mientras hace revisiones de código Python, y hasta ahora debería estar claro por qué. La cantidad de información que podría haber agregado a las declaraciones de depuración, los informes de fallos o los archivos de registro con un método simple es abrumadora en comparación con la información más escasa y, a menudo, poco útil (tipo, id) que se proporciona de forma predeterminada.

Si desea diferentes representaciones para cuando, por ejemplo, dentro de un contenedor, querrá implementar los métodos __repr__ y __str__ . (Más sobre cómo puede usar estos dos métodos de manera diferente a continuación).

Ambos métodos implementados, eval-round-trip style __repr __ ()

class Card:
    special_names = {1:'Ace', 11:'Jack', 12:'Queen', 13:'King'}

    def __init__(self, suit, pips):
        self.suit = suit
        self.pips = pips

    # Called when instance is converted to a string via str()
    # Examples:
    #   print(card1)
    #   print(str(card1)
    def __str__(self):
        card_name = Card.special_names.get(self.pips, str(self.pips))
        return "%s of %s" % (card_name, self.suit)

    # Called when instance is converted to a string via repr()
    # Examples:
    #   print([card1, card2, card3])
    #   print(repr(card1))
    def __repr__(self):
        return "Card(%s, %d)" % (self.suit, self.pips)


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow