Python Language
Rappresentazioni di stringhe di istanze di classe: metodi __str__ e __repr__
Ricerca…
Osservazioni
Una nota sull'implementazione di entrambi i metodi
Quando entrambi i metodi sono implementati, è piuttosto comune avere un metodo __str__
che restituisca una rappresentazione umana (ad es. "Ace of Spaces") e __repr__
restituisca una rappresentazione di eval
-friendly.
In effetti, i documenti Python per repr()
annotano esattamente questo:
Per molti tipi, questa funzione fa un tentativo di restituire una stringa che produce un oggetto con lo stesso valore quando viene passato a eval (), altrimenti la rappresentazione è una stringa racchiusa tra parentesi angolari che contiene il nome del tipo dell'oggetto con informazioni aggiuntive spesso incluso il nome e l'indirizzo dell'oggetto.
Ciò significa che __str__
potrebbe essere implementato per restituire qualcosa come "Ace of Spaces" come mostrato in precedenza, __repr__
potrebbe essere implementato per restituire invece Card('Spades', 1)
Questa stringa potrebbe essere passata direttamente indietro in eval
in un po 'di "round trip":
object -> string -> object
Un esempio di implementazione di tale metodo potrebbe essere:
def __repr__(self):
return "Card(%s, %d)" % (self.suit, self.pips)
Gli appunti
[1] Questo output è specifico per l'implementazione. La stringa visualizzata proviene da cpython.
[2] Potresti aver già visto il risultato di questa divisione str()
/ repr()
e non averlo conosciuto. Quando stringhe contenenti caratteri speciali come barre retroverse vengono convertite in stringhe tramite str()
le barre retroverse appaiono così come sono (compaiono una volta). Quando vengono convertiti in stringhe tramite repr()
(ad esempio, come elementi di una lista visualizzata), i backslash sono sfuggiti e quindi appaiono due volte.
Motivazione
Quindi hai appena creato la tua prima classe in Python, una piccola e bella classe che incapsula una carta da gioco:
class Card:
def __init__(self, suit, pips):
self.suit = suit
self.pips = pips
Altrove nel tuo codice, crei alcune istanze di questa classe:
ace_of_spades = Card('Spades', 1)
four_of_clubs = Card('Clubs', 4)
six_of_hearts = Card('Hearts', 6)
Hai persino creato un elenco di carte, per rappresentare una "mano":
my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]
Ora, durante il debug, vuoi vedere come appare la tua mano, quindi fai ciò che viene naturale e scrivi:
print(my_hand)
Ma quello che ottieni è un mucchio di parole senza senso:
[<__main__.Card instance at 0x0000000002533788>,
<__main__.Card instance at 0x00000000025B95C8>,
<__main__.Card instance at 0x00000000025FF508>]
Confuso, provi solo a stampare una singola carta:
print(ace_of_spades)
E ancora, ottieni questa strana uscita:
<__main__.Card instance at 0x0000000002533788>
Non avere paura. Stiamo per risolvere questo problema.
Innanzitutto, è importante capire cosa sta succedendo qui. Quando hai scritto print(ace_of_spades)
hai detto a Python che volevi che print(ace_of_spades)
informazioni sull'istanza Card
tuo codice chiama ace_of_spades
. E per essere onesti, lo ha fatto.
Quell'output è composto da due bit importanti: il type
dell'oggetto e l' id
dell'oggetto. La seconda parte da sola (il numero esadecimale) è sufficiente per identificare univocamente l'oggetto al momento della chiamata di print
. [1]
Ciò che è realmente accaduto è che hai chiesto a Python di "mettere in parole" l'essenza di quell'oggetto e poi mostrarlo a te. Una versione più esplicita dello stesso macchinario potrebbe essere:
string_of_card = str(ace_of_spades)
print(string_of_card)
Nella prima riga, provi a trasformare l'istanza di Card
in una stringa, e nel secondo la visualizzi.
Il problema
Il problema che stai riscontrando deriva dal fatto che, mentre hai detto a Python tutto ciò che doveva sapere sulla classe Card
per poter creare le carte, non hai detto come volevi convertire le istanze di Card
in stringhe.
E poiché non sapeva, quando tu (implicitamente) hai scritto str(ace_of_spades)
, ti ha dato ciò che hai visto, una rappresentazione generica dell'istanza Card
.
La soluzione (parte 1)
Ma possiamo dire a Python come vogliamo che le istanze delle nostre classi personalizzate vengano convertite in stringhe. E il modo in cui lo facciamo è con il metodo __str__
"dunder" (per doppia sottolineatura) o "magico".
Ogni volta che dici a Python di creare una stringa da un'istanza di classe, cercherà un metodo __str__
sulla classe e lo chiamerà.
Considera la seguente versione aggiornata della nostra classe 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)
Qui, abbiamo definito il metodo __str__
sulla nostra classe Card
che, dopo una semplice ricerca del dizionario per le face card, restituisce una stringa formattata, tuttavia decidiamo noi.
(Si noti che "restituisce" è in grassetto qui, per sottolineare l'importanza di restituire una stringa, e non semplicemente stamparla. La stampa può sembrare funzionare, ma poi si dovrebbe stampare la scheda quando si fa qualcosa come str(ace_of_spades)
, senza nemmeno avere una chiamata di funzione di stampa nel programma principale. Per essere chiari, assicurarsi che __str__
restituisca una stringa.).
Il metodo __str__
è un metodo, quindi il primo argomento sarà self
e non dovrebbe né accettare né passare argomenti addizionali.
Tornando al nostro problema di visualizzare la carta in un modo più user-friendly, se eseguiamo nuovamente:
ace_of_spades = Card('Spades', 1)
print(ace_of_spades)
Vedremo che il nostro output è molto migliore:
Ace of Spades
Così bello, abbiamo finito, giusto?
Beh, solo per coprire le nostre basi, ricontrolliamo che abbiamo risolto il primo problema che abbiamo incontrato, stampando l'elenco delle istanze di Card
, la hand
.
Quindi ricontrolliamo il seguente codice:
my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]
print(my_hand)
E, con nostra sorpresa, otteniamo di nuovo quei divertenti codici esadecimali:
[<__main__.Card instance at 0x00000000026F95C8>,
<__main__.Card instance at 0x000000000273F4C8>,
<__main__.Card instance at 0x0000000002732E08>]
Cosa sta succedendo? Abbiamo detto a Python come volevamo visualizzare le nostre istanze di Card
, perché apparentemente sembrava che dimenticasse?
La soluzione (parte 2)
Bene, il macchinario dietro le quinte è un po 'diverso quando Python vuole ottenere la rappresentazione di stringa degli elementi in una lista. Risulta che a Python non interessa __str__
per questo scopo.
Invece, sembra per un metodo diverso, __repr__
, e se questo non è trovato, cade di nuovo sul "cosa esadecimale". [2]
Quindi stai dicendo che devo fare due metodi per fare la stessa cosa? Uno per quando voglio print
mia carta da solo e un altro quando è in una sorta di contenitore?
No, ma prima diamo un'occhiata a come sarebbe la nostra classe se dovessimo implementare entrambi i metodi __str__
e __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)
Qui, l'implementazione dei due metodi __str__
e __repr__
sono esattamente gli stessi, tranne per il fatto che, per distinguere tra i due metodi, (S)
viene aggiunto alle stringhe restituite da __str__
e (R)
viene aggiunto alle stringhe restituite da __repr__
.
Nota che proprio come il nostro metodo __str__
, __repr__
non accetta argomenti e restituisce una stringa.
Ora possiamo vedere quale metodo è responsabile per ogni 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)
Come è stato trattato, il metodo __str__
è stato chiamato quando abbiamo passato la nostra istanza Card
per print
e il metodo __repr__
è stato chiamato quando abbiamo passato un elenco delle nostre istanze da print
.
A questo punto vale la pena sottolineare che, come possiamo creare esplicitamente una stringa da un'istanza di classe personalizzata usando str()
come abbiamo fatto in precedenza, possiamo anche creare esplicitamente una rappresentazione di stringa della nostra classe con una funzione incorporata chiamata repr()
.
Per esempio:
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)
E inoltre, se definito, potremmo chiamare i metodi direttamente (anche se sembra un po 'poco chiaro ed inutile):
print(four_of_clubs.__str__()) # 4 of Clubs (S)
print(four_of_clubs.__repr__()) # 4 of Clubs (R)
Informazioni su quelle funzioni duplicate ...
Gli sviluppatori Python si sono resi conto, nel caso in cui volevi che stringhe identiche venissero restituite da str()
e repr()
, potresti dover duplicare funzionalmente i metodi, qualcosa che a nessuno piace.
Quindi, invece, c'è un meccanismo in atto per eliminare la necessità di ciò. Uno che ti ho fatto passare fino a questo punto. Si scopre che se una classe implementa il metodo __repr__
ma non il metodo __str__
e si passa un'istanza di quella classe a str()
(sia implicitamente che esplicitamente), Python eseguirà il fallback sull'implementazione __repr__
e lo userà.
Quindi, per essere chiari, considera la seguente versione della classe 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)
Nota che questa versione implementa solo il metodo __repr__
. Tuttavia, le chiamate a str()
provocano la versione user-friendly:
print(six_of_hearts) # 6 of Hearts (implicit conversion)
print(str(six_of_hearts)) # 6 of Hearts (explicit conversion)
come le chiamate a repr()
:
print([six_of_hearts]) #[6 of Hearts] (implicit conversion)
print(repr(six_of_hearts)) # 6 of Hearts (explicit conversion)
Sommario
Per consentire alle istanze di classe di "mostrarsi" in modo __repr__
, ti consigliamo di implementare almeno il metodo __repr__
della tua classe. Se la memoria serve, durante una __repr__
Raymond Hettinger ha affermato che garantire l'implementazione di classi __repr__
è una delle prime cose che cerca mentre si eseguono le revisioni del codice Python, e ormai dovrebbe essere chiaro il perché. La quantità di informazioni che potresti aver aggiunto alle istruzioni di debug, ai rapporti sugli arresti anomali o ai file di registro con un metodo semplice è schiacciante se confrontata con le informazioni irrisorie e spesso meno utili (tipo, id) fornite per impostazione predefinita.
Se desideri rappresentazioni diverse per quando, ad esempio, all'interno di un contenitore, dovrai implementare entrambi i metodi __repr__
e __str__
. (Ulteriori informazioni su come si potrebbero usare questi due metodi in modo diverso sotto).
Entrambi i metodi implementati, 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)