Inleiding
Vaak vragen studenten wat het verschil is tussen de dunders __str__
en __repr__
in een Python-klasse. Een begrijpelijke vraag, want intuïtief doen beide methoden hetzelfde: ze geven een string-representatie van het object in kwestie. Dus wat zijn inderdaad de overeenkomsten en verschillen?
Er zijn natuurlijk al diverse blog-posts die dit uitleggen. Zo is er een uitgebreide beschrijving op journaldev.com, of deze uitleg op StackOverflow. Maar het leek me handig om een Nederlandstalige beschrijving te maken, waarbij ook wordt ingegaan op de momenten waarop beide methoden worden aangeroepen.
Wat zegt de documentatie?
De documentatie op python.org lijkt in eerste instantie duidelijk. De __repr__
-methode geeft de ‘officiële’ string-represenatie van het object terug, terwijl de __str__
-methode de ‘informele’ of netjes afdrukbare representatie van het object terug moet geven. Let op dat deze documentatie gebruik maakt van apostrofen om aan te geven dat zij ook niet precies definiëren wat een ‘officieële’ of ‘informele’ is.
Hoewel, dat is niet helemaal waar. De documentatie bij __repr__
vervolgt met de opmerking dat de string die deze methode teruggeeft een valide Python-expressie is die in het ideale geval gebruikt kan worden om het object opnieuw te genereren. In die betekenis is __repr__
dus een soort serialisatie methode.
Een verschil in aanroepen
Laten we eens kijken wanneer de verschillende methoden worden aangeroepen.
In [1]: class Demo:
...: pass
...:
...: d = Demo()
...: print (d)
...:
...: print (d.__repr__())
...: print (d.__str__())
<__main__.Demo object at 0x7f8a1441bf70>
<__main__.Demo object at 0x7f8a1441bf70>
<__main__.Demo object at 0x7f8a1441bf70>
In [2]:
De output van al deze print-statements is in alle gevallen hetzelfde: '<__main__.Demo object at 0x7fe0adc4d910>'
. Dat is niet heel zinvolle informatie, en het is ook logisch dat bede methoden dezelfde output geven: de implementatie van __str__
in Object
roept gewoon __repr__
aan.
Wat dan, wanneer we één van beide methoden in onze klasse implementeren?
In [1]: class Demo:
...: def __init__(self):
...: self.demostr = 'hallo allemaal'
...:
...: def __repr__(self):
...: return f'Demo object met demostr: {self.demostr}'
...:
...: d = Demo()
...: print (d)
Demo object met demostr: hallo allemaal
In [2]:
Het resultaat van deze test is de string die door __repr__
wordt teruggegeven: Demo object met demostr: hallo allemaal
. Blijkbaar wordt door print
een call gedaan naar __repr__
. Maar wat nu als we beide methoden implementen?
In [1]: class Demo:
...: def __init__(self):
...: self.demostr = 'hallo allemaal'
...:
...: def __repr__(self):
...: return f'Demo object met demostr: {self.demostr}'
...:
...: def __str__(self):
...: return f'De str-methode aangeroepen.'
...:
...:
...: d = Demo()
...: print (d)
De str-methode aangeroepen.
In [2]:
Dit maakt duidelijk dat er een call naar __str__
gedaan wordt. Blijkbaar kijkt Python bij een print
eerst of er een implementatie van __str__
bestaat; als dat niet het geval is, wordt een poging gedaan __repr__
aan te roepen, en als die er ook niet is, wordt hetzelfde protocol bij de superklasse toegepast. En zo verder totdat we bij Object
aankomen.
Het automatisch aanroepen van __repr__
Dus print
roept automatisch __str__
aan. Maar is er ook een situatie denkbaar waarin automatisch __repr__
wordt aangeroepen, zelfs wanneer beide methoden bestaan? Dit blijkt het geval te zijn wanneer we het object uitprinten zonder gebruik te maken van de methode print
, zoals we vaak doen in de interactieve shell:
In [1]: class Demo:
...: def __init__(self):
...: self.demostr = 'hallo allemaal'
...:
...: def __repr__(self):
...: return f'Demo object met demostr: {self.demostr}'
...:
...: def __str__(self):
...: return f'De str-methode aangeroepen.'
...:
...:
...: d = Demo()
...: print (d)
De str-methode aangeroepen.
In [2]: d
Out[2]: Demo object met demostr: hallo allemaal
In [3]:
Een andere situatie waarin __repr__
wordt aangeroepen is wanneer het object een onderdeel is van een tupel
of een dictionary
die wordt uitgeprint:
In [4]: print ([d])
[Demo object met demostr: hallo allemaal]
In [5]: print ({'key':d})
{'key': Demo object met demostr: hallo allemaal}
In [7]:
Serialisatie door middel van __repr__
Zoals aangegeven, is het de bedoeling van __repr__
dat deze methode een representatie teruggeeft die in principe gebruikt kan worden om het object te regenereren. Dat betekent dat obj == eval(repr(obj))
in de regel een waarde zou moeten hebben van True
(dat kan niet altijd natuurlijk, maar dit is een ideaal). Hoe zou je zoiets kunnen realiseren?
In principe is een object niks meer dan een zooi methoden met een interne status. Dus om een op basis van een bestaan object een nieuw object te maken, volstaat het om deze interne status aan het nieuwe object door te geven. Zolang dat nieuwe object van hetzelfde type is, heeft dat object ook de gewenste methoden.
class Demo:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def update(self, key, value):
self.__dict__[key] = value
def __str__(self):
return f'Demo object, verder niks aan de hand'
def __repr__(self):
return self.__dict__
# maak een object aan met wat data
object1 = Demo(naam='Bart', woonplaats='Groningen', huisnummer=116)
# verander de interne status van het object
object1.update('huisnummer', 200)
# en printen de interne status
print (object1.__repr__())
# hier maken we een kopie aan van het eerste object
object2 = Demo(**object1.__repr__())
# en printen de interne status; die is hetzelfde als object1
print (object2.__repr__())
# nu passen we de interne status van het eerste object aan
object2.update('huisnummer', 60)
# en printen van beide objecten de interne status af
print (object1.__repr__())
print (object2.__repr__())
De code hierboven levert het volgende op:
{'naam': 'Bart', 'woonplaats': 'Groningen', 'huisnummer': 116}
{'naam': 'Bart', 'woonplaats': 'Groningen', 'huisnummer': 200}
{'naam': 'Bart', 'woonplaats': 'Groningen', 'huisnummer': 200}
{'naam': 'Bart', 'woonplaats': 'Groningen', 'huisnummer': 60}
Conclusie
Dus __str__
en __repr__
lijken er op elkaar; ze verschillen op nuance maar daardoor wel radicaal. Het beste advies voor de alledaagse gang van zaken is gewoon in de eigen klassen __str__
te implementeren, en __repr__
te gebruiken voor klassen die geserialiseerd moeten worden.