Python Language
Strängrepresentationer av klassinstanser: __str__ och __repr__ metoder
Sök…
Anmärkningar
En anteckning om att implementera båda metoderna
När båda metoderna implementeras är det något vanligt att ha en __str__
metod som returnerar en mänsklig vänlig representation (t.ex. "Ace of Spaces") och __repr__
returnerar en eval
vänlig representation.
Faktum är att Python dokumenterar för repr()
notera exakt detta:
För många typer gör denna funktion ett försök att returnera en sträng som skulle ge ett objekt med samma värde när det skickas till eval (), annars är representationen en sträng innesluten i vinkelparenteser som innehåller namnet på objektets typ tillsammans med ytterligare information ofta inklusive objektets namn och adress.
Vad det betyder är att __str__
kan implementeras för att returnera något som "Ace of Spaces" som tidigare visats, __repr__
kan implementeras för att istället returnera Card('Spades', 1)
Denna sträng kan skickas direkt tillbaka till eval
i något av en "tur-retur":
object -> string -> object
Ett exempel på en implementering av en sådan metod kan vara:
def __repr__(self):
return "Card(%s, %d)" % (self.suit, self.pips)
anteckningar
[1] Denna utgång är implementeringsspecifik. Strängen som visas är från cpython.
[2] Du kanske redan har sett resultatet av denna str()
/ repr()
-delning och inte känt den. När strängar som innehåller specialtecken som backslits konverteras till strängar via str()
visas backstänkarna som de är (de visas en gång). När de konverteras till strängar via repr()
(till exempel som element i en lista som visas), flyttas bakstreck och visas därför två gånger.
Motivering
Så du har precis skapat din första klass i Python, en snygg liten klass som innehåller ett spelkort:
class Card:
def __init__(self, suit, pips):
self.suit = suit
self.pips = pips
På andra håll i din kod skapar du några instanser av den här klassen:
ace_of_spades = Card('Spades', 1)
four_of_clubs = Card('Clubs', 4)
six_of_hearts = Card('Hearts', 6)
Du har till och med skapat en lista med kort för att representera en "hand":
my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]
Nu, under felsökning, vill du se hur din hand ser ut så att du gör det som kommer naturligt och skriver:
print(my_hand)
Men vad du får tillbaka är en massa gibberish:
[<__main__.Card instance at 0x0000000002533788>,
<__main__.Card instance at 0x00000000025B95C8>,
<__main__.Card instance at 0x00000000025FF508>]
Förvirrad, du försöker bara skriva ut ett enda kort:
print(ace_of_spades)
Och igen får du denna konstiga utgång:
<__main__.Card instance at 0x0000000002533788>
Var inte rädd. Vi håller på att fixa det här.
Först är det dock viktigt att förstå vad som händer här. När du skrev print(ace_of_spades)
sa du till Python att du ville att den skulle skriva ut information om Card
din kod ringer ace_of_spades
. Och för att vara rättvis gjorde det det.
Denna utgång består av två viktiga bitar: type
av objekt och objektets id
. Enbart den andra delen (den hexadecimala tal) är tillräckligt för att unikt identifiera objektet vid tidpunkten för print
samtalet. [1]
Det som verkligen hände var att du bad Python att "sätta ord på" objektets essens och sedan visa det för dig. En mer tydlig version av samma maskiner kan vara:
string_of_card = str(ace_of_spades)
print(string_of_card)
I den första raden försöker du förvandla din Card
till en sträng och i den andra visar du den.
Problemet
Det problem du stöter på uppstår på grund av det faktumet att medan du berättade för Python allt det behövde att veta om Card
för att skapa kort, berättade du inte hur du ville att Card
skulle konverteras till strängar.
Och eftersom det inte visste, när du (implicit) skrev str(ace_of_spades)
, gav det dig vad du såg, en generisk representation av Card
.
Lösningen (del 1)
Men vi kan berätta för Python hur vi vill att instanser av våra anpassade klasser ska konverteras till strängar. Och hur vi gör detta är med __str__
"dunder" (för dubbel understruk) eller "magi" -metoden.
När du ber Python att skapa en sträng från en klassinstans kommer den att leta efter en __str__
metod i klassen och ringa den.
Tänk på följande, uppdaterade versionen av vår 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)
Här har vi nu definierat __str__
metoden i vår Card
, som efter en enkel uppslagning av ansiktskort i ordboken returnerar en sträng formaterad men vi bestämmer oss.
(Observera att "returnerar" är i fetstil här, för att betona vikten av att returnera en sträng och inte bara skriva ut den. Skriva ut kan tyckas fungera, men då skulle du få kortet tryckt när du gjorde något som str(ace_of_spades)
, utan att ens ha ett utskriftsfunktionssamtal i ditt huvudprogram. För att vara tydlig, se till att __str__
returnerar en sträng.).
Metoden __str__
är en metod, så det första argumentet kommer att vara self
och det bör varken acceptera eller heller vidarebefordras ytterligare argument.
Återvända till vårt problem med att visa kortet på ett mer användarvänligt sätt om vi igen kör:
ace_of_spades = Card('Spades', 1)
print(ace_of_spades)
Vi ser att vår produktion är mycket bättre:
Ace of Spades
Så bra, vi är klara, eller hur?
Tja bara för att täcka våra grunder, låt oss dubbelkontrollera att vi har löst det första problemet vi stött på, trycka ut listan över Card
, hand
.
Så vi kontrollerar följande kod:
my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]
print(my_hand)
Och till vår överraskning får vi de roliga hexkoderna igen:
[<__main__.Card instance at 0x00000000026F95C8>,
<__main__.Card instance at 0x000000000273F4C8>,
<__main__.Card instance at 0x0000000002732E08>]
Vad pågår? Vi berättade för Python hur vi ville att våra Card
skulle visas, varför tycktes det tydligen glömma det?
Lösningen (del 2)
Tja, maskinerna bakom kulisserna är lite annorlunda när Python vill få strängrepresentationen av objekt i en lista. Det visar sig att Python inte bryr sig om __str__
för detta ändamål.
Istället letar det efter en annan metod, __repr__
, och om det inte hittas faller det tillbaka på "hexidecimala saken". [2]
Så du säger att jag måste göra två metoder för att göra samma sak? En för när jag vill print
mitt kort av sig själv och ett annat när det är i någon slags behållare?
Nej, men låt oss först titta på hur vår klass skulle vara om vi skulle implementera både __str__
och __repr__
metoder:
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)
Här är implementeringen av de två metoderna __str__
och __repr__
exakt desamma, förutom att för att skilja mellan de två metoderna läggs (S)
till strängar som returneras av __str__
och (R)
läggs till strängar som returneras av __repr__
.
Observera att precis som vår __str__
metod accepterar __repr__
inga argument och returnerar en sträng.
Vi kan nu se vilken metod som är ansvarig för varje enskilt fall:
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)
Som behandlades __str__
metoden när vi passerade vår Card
att print
och __repr__
metoden kallades när vi passerade en lista över våra instanser att print
.
Vid denna punkt är det värt att påpeka att precis som vi uttryckligen kan skapa en sträng från en anpassad klassinstans med str()
som vi gjorde tidigare, kan vi också uttryckligen skapa en strängrepresentation av vår klass med en inbyggd funktion som kallas repr()
.
Till exempel:
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)
Och dessutom, om definierat, vi kan kalla de metoder som direkt (även om det verkar lite otydlig och onödigt):
print(four_of_clubs.__str__()) # 4 of Clubs (S)
print(four_of_clubs.__repr__()) # 4 of Clubs (R)
Om de duplicerade funktionerna ...
Python-utvecklare insåg att om du vill att identiska strängar ska returneras från str()
och repr()
kan du behöva funktionellt duplicera metoder - något ingen gillar.
Så istället finns det en mekanism för att eliminera behovet av det. En jag slog dig förbi fram till denna punkt. Det visar sig att om en klass implementerar __repr__
metoden men inte __str__
metoden, och du skickar en instans av den klassen till str()
(antingen implicit eller uttryckligen), kommer Python att falla tillbaka på din __repr__
implementering och använda det.
Så för att vara tydlig, överväga följande version av 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)
Observera att den här versionen bara implementerar __repr__
metoden. Samtidigt resulterar samtal till str()
i den användarvänliga versionen:
print(six_of_hearts) # 6 of Hearts (implicit conversion)
print(str(six_of_hearts)) # 6 of Hearts (explicit conversion)
liksom samtal till repr()
:
print([six_of_hearts]) #[6 of Hearts] (implicit conversion)
print(repr(six_of_hearts)) # 6 of Hearts (explicit conversion)
Sammanfattning
För att du ska ge dina klassinstanser att "visa sig" på användarvänliga sätt, vill du överväga att implementera minst klassens __repr__
metod. Om minnet tjänar, sade Raymond Hettinger att ett klasser implementerar __repr__
är en av de första sakerna han letar efter när han gjorde Python-kodrecensioner, och nu bör det vara klart varför. Mängden information som du kan ha lagt till i felsökningsuttalanden, kraschrapporter eller loggfiler med en enkel metod är överväldigande jämfört med den vackra, och ofta mindre information än som är användbar (typ, id) som ges som standard.
Om du vill ha olika representationer för när, till exempel, inuti en behållare, vill du implementera både __repr__
och __str__
metoder. (Mer om hur du kan använda dessa två metoder på olika sätt nedan).
Båda metoderna implementerade, eval-rundtur-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)