Python Language
Zeichenfolgendarstellungen von Klasseninstanzen: __str__- und __repr__-Methoden
Suche…
Bemerkungen
Ein Hinweis zum Implementieren beider Methoden
Wenn beide Methoden implementiert werden, ist es üblich, eine __str__
Methode zu verwenden, die eine menschenfreundliche Darstellung (z. B. "Ace of Spaces") und __repr__
eine eval
freundliche Darstellung zurückgibt.
Tatsächlich beachten die Python-Dokumente für repr()
genau das repr()
:
Bei vielen Typen versucht diese Funktion, eine Zeichenfolge zurückzugeben, die ein Objekt mit demselben Wert ergibt, wenn sie an eval () übergeben wird. Andernfalls handelt es sich bei der Darstellung um eine in spitze Klammern eingeschlossene Zeichenfolge, die den Namen des Objekttyps enthält mit zusätzlichen Informationen, die häufig den Namen und die Adresse des Objekts enthalten.
Das bedeutet, dass __str__
so implementiert werden könnte, dass etwas wie "Ass der Räume" zurückgegeben wird. __repr__
könnte so implementiert werden, dass stattdessen Card('Spades', 1)
Diese Zeichenfolge könnte in einem " eval
" direkt an eval
:
object -> string -> object
Ein Beispiel für eine Implementierung einer solchen Methode könnte sein:
def __repr__(self):
return "Card(%s, %d)" % (self.suit, self.pips)
Anmerkungen
[1] Diese Ausgabe ist implementierungsspezifisch. Die angezeigte Zeichenfolge stammt von cpython.
[2] Möglicherweise haben Sie das Ergebnis dieser str()
/ repr()
str()
bereits gesehen und nicht erkannt. Wenn Strings, die Sonderzeichen wie Backslashes enthalten, über str()
in Strings konvertiert werden, erscheinen die Backslashes so, wie sie sind (sie erscheinen einmal). Wenn sie über repr()
in Strings konvertiert werden (z. B. als Elemente einer angezeigten Liste), werden die Backslashes mit Escapezeichen versehen und erscheinen daher zweimal.
Motivation
Sie haben also gerade Ihre erste Klasse in Python erstellt, eine nette kleine Klasse, die eine Spielkarte enthält:
class Card:
def __init__(self, suit, pips):
self.suit = suit
self.pips = pips
An anderer Stelle in Ihrem Code erstellen Sie einige Instanzen dieser Klasse:
ace_of_spades = Card('Spades', 1)
four_of_clubs = Card('Clubs', 4)
six_of_hearts = Card('Hearts', 6)
Sie haben sogar eine Liste von Karten erstellt, um eine "Hand" darzustellen:
my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]
Beim Debuggen möchten Sie nun sehen, wie Ihre Hand aussieht, also tun Sie, was natürlich kommt und schreiben
print(my_hand)
Aber was Sie zurückbekommen, ist ein Haufen Kauderwelsch:
[<__main__.Card instance at 0x0000000002533788>,
<__main__.Card instance at 0x00000000025B95C8>,
<__main__.Card instance at 0x00000000025FF508>]
Verwirrt versuchen Sie, nur eine einzige Karte zu drucken:
print(ace_of_spades)
Und wieder erhalten Sie diese seltsame Ausgabe:
<__main__.Card instance at 0x0000000002533788>
Hab keine Angst. Wir sind dabei, das zu beheben.
Zunächst ist es jedoch wichtig zu verstehen, was hier vor sich geht. Wenn Sie schreibt print(ace_of_spades)
sagten Sie Python man es will Informationen über die drucken Card
Instanz Code ruft ace_of_spades
. Und um fair zu sein, tat es.
Diese Ausgabe besteht aus zwei wichtigen Bits: dem type
des Objekts und der id
des Objekts. Allein der zweite Teil (die Hexadezimalzahl) reicht aus, um das Objekt zum Zeitpunkt des print
eindeutig zu identifizieren. [1]
Was wirklich los war, war, dass Sie Python gebeten haben, die Essenz dieses Objekts "in Worte zu fassen" und es Ihnen dann anzuzeigen. Eine explizitere Version derselben Maschine könnte sein:
string_of_card = str(ace_of_spades)
print(string_of_card)
In der ersten Zeile versuchen Sie, Ihre Card
in eine Zeichenfolge umzuwandeln, und in der zweiten zeigen Sie sie an.
Das Problem
Das Problem, auf das Sie stoßen, ist darauf zurückzuführen, dass Sie Python zwar alles erzählten, was Sie über die Card
wissen mussten, um Karten zu erstellen , aber nicht , wie Sie Card
in Zeichenketten konvertieren wollten.
Und da es nicht wusste, als Sie (implizit) str(ace_of_spades)
, hat es Ihnen das gegeben, was Sie gesehen haben, eine generische Darstellung der Card
Instanz.
Die Lösung (Teil 1)
Wir können Python jedoch mitteilen, wie Instanzen unserer benutzerdefinierten Klassen in Strings konvertiert werden sollen. Und so machen wir das mit der __str__
__- __str__
"dunder" (für doppelten Unterstrich) oder "magic".
Immer, wenn Sie Python __str__
, eine Zeichenfolge aus einer Klasseninstanz zu erstellen, sucht es nach einer __str__
Methode für die Klasse und ruft sie auf.
Betrachten Sie die folgende aktualisierte Version unserer Card
Klasse:
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)
Hier haben wir nun die __str__
Methode in unserer Card
Klasse definiert, die nach einem einfachen Nachschlagen des Wörterbuchs für Bildkarten eine formatierte Zeichenfolge zurückgibt , je nachdem, wie wir uns entscheiden.
(Beachten Sie, dass "Returns" hier fett gedruckt ist, um zu str(ace_of_spades)
, wie wichtig es ist, einen String zurückzugeben und nicht einfach nur zu drucken. Das Drucken scheint zwar zu funktionieren, aber Sie haben die Karte dann gedruckt, wenn Sie etwas wie str(ace_of_spades)
, ohne dass in Ihrem Hauptprogramm sogar eine Funktion zum Drucken aufgerufen wird. __str__
sicher, dass __str__
eine Zeichenfolge zurückgibt.
Die __str__
Methode ist eine Methode. Das erste Argument ist also self
und sollte weder zusätzliche Argumente annehmen noch übergeben.
Um auf das Problem zurückzukommen, die Karte benutzerfreundlicher darzustellen, wenn wir erneut ausführen:
ace_of_spades = Card('Spades', 1)
print(ace_of_spades)
Wir werden sehen, dass unsere Ausgabe viel besser ist:
Ace of Spades
So toll, wir sind fertig, richtig?
Um unsere Grundlagen zu verdeutlichen, überprüfen wir noch einmal, ob wir das erste Problem gelöst haben und die Liste der Card
, der hand
, gedruckt haben.
Also überprüfen wir den folgenden Code noch einmal:
my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]
print(my_hand)
Und zu unserer Überraschung bekommen wir wieder diese lustigen Hex-Codes:
[<__main__.Card instance at 0x00000000026F95C8>,
<__main__.Card instance at 0x000000000273F4C8>,
<__main__.Card instance at 0x0000000002732E08>]
Was ist los? Wir erzählten Python, wie wir wollten, dass unsere Card
angezeigt werden. Warum schien sie anscheinend zu vergessen?
Die Lösung (Teil 2)
Die Maschinerie hinter den Kulissen ist etwas anders, wenn Python die Zeichenfolgendarstellung von Elementen in einer Liste erhalten möchte. Es stellt sich heraus, dass sich Python für diesen Zweck nicht für __str__
.
Stattdessen sucht es nach einer anderen Methode, __repr__
, und wenn diese nicht gefunden wird, greift sie auf das "Hexadezimal-Ding" zurück. [2]
Sie sagen also, ich muss zwei Methoden entwickeln, um dasselbe zu tun? Eine, wenn ich meine Karte alleine print
möchte, und eine andere, wenn sie sich in einem Container befindet?
Nein, aber zuerst schauen wir uns an, wie unsere Klasse aussehen würde , wenn wir beide __str__
und __repr__
Methoden implementieren __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)
Hier ist die Implementierung der beiden Methoden __str__
und __repr__
genau gleich, mit der Ausnahme, dass zur Unterscheidung der beiden Methoden (S)
zu den von __str__
Zeichenfolgen __str__
und (R)
zu den von __repr__
Zeichenfolgen __repr__
.
Beachten Sie, dass genau wie unsere __str__
Methode, __repr__
keine Argumente akzeptiert und gibt einen String zurück.
Wir können jetzt sehen, welche Methode für jeden Fall verantwortlich ist:
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)
Wie bedeckt war, die __str__
wurde Methode aufgerufen , wenn wir unsere übergeben Card
Instanz print
und die __repr__
Methode aufgerufen wurde , wenn wir eine Liste unserer Instanzen weitergegeben zu print
.
An dieser Stelle sei darauf hingewiesen, dass wir, genau wie zuvor mit str()
explizit einen String aus einer benutzerdefinierten Klasseninstanz erstellen können, auch explizit eine String-Darstellung unserer Klasse mit einer eingebauten Funktion namens repr()
erstellen können. repr()
.
Zum Beispiel:
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)
Außerdem könnten wir, wenn definiert, die Methoden direkt aufrufen (obwohl dies etwas unklar und unnötig erscheint):
print(four_of_clubs.__str__()) # 4 of Clubs (S)
print(four_of_clubs.__repr__()) # 4 of Clubs (R)
Über diese duplizierten Funktionen ...
Python-Entwickler erkannten, dass in dem Fall, dass identische Zeichenfolgen von str()
und repr()
sollen, Methoden möglicherweise funktional dupliziert werden müssen - etwas, das niemandem gefällt.
Stattdessen gibt es einen Mechanismus, um die Notwendigkeit dafür zu beseitigen. Einer, an dem ich dich bis hierher geschlichen habe. Es stellt sich heraus, dass, wenn eine Klasse die __repr__
Methode aber nicht die __str__
__repr__
Methode implementiert und Sie eine Instanz dieser Klasse an str()
(ob implizit oder explizit), Python auf Ihre __repr__
Implementierung __repr__
und diese verwendet.
Um klar zu sein, betrachten Sie die folgende Version der Card
Klasse:
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)
Beachten Sie, dass diese Version nur die __repr__
Methode implementiert. Aufrufe von str()
führen jedoch zu der benutzerfreundlichen Version:
print(six_of_hearts) # 6 of Hearts (implicit conversion)
print(str(six_of_hearts)) # 6 of Hearts (explicit conversion)
wie Aufrufe von repr()
:
print([six_of_hearts]) #[6 of Hearts] (implicit conversion)
print(repr(six_of_hearts)) # 6 of Hearts (explicit conversion)
Zusammenfassung
Damit Sie Ihre Klasseninstanzen dazu befähigen können, sich auf benutzerfreundliche Weise "zu zeigen", sollten Sie mindestens die __repr__
Methode Ihrer Klasse __repr__
. Wenn das Gedächtnis während eines Gesprächs dient, sagte Raymond Hettinger, dass die Sicherstellung, dass __repr__
für Klassen implementiert wird, eines der ersten Dinge ist, nach denen er bei Python-Code-Reviews sucht, und jetzt sollte klar sein, warum. Die Menge an Informationen, die Sie zum Debuggen von Anweisungen, Absturzberichten oder Protokolldateien mit einer einfachen Methode hinzugefügt haben könnten , ist überwältigend, wenn Sie sie mit dem bloßen vergleichen, und oft weniger nützliche Informationen (Typ, ID), die standardmäßig bereitgestellt werden.
Wenn Sie unterschiedliche Repräsentationen für beispielsweise innerhalb eines Containers wünschen, sollten Sie sowohl die Methoden __repr__
als auch __str__
implementieren. (Mehr darüber, wie Sie diese beiden Methoden auf unterschiedliche Weise verwenden können).
Beide Methoden implementiert, Eval-Round-Trip-Stil __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)