{"id":1086,"date":"2021-03-18T14:25:56","date_gmt":"2021-03-18T13:25:56","guid":{"rendered":"https:\/\/www.bartbarnard.nl\/blog\/?p=1086"},"modified":"2021-03-18T14:25:56","modified_gmt":"2021-03-18T13:25:56","slug":"python-string-representaties","status":"publish","type":"post","link":"https:\/\/www.bartbarnard.nl\/blog\/python-string-representaties\/","title":{"rendered":"Python string representaties"},"content":{"rendered":"<h2 id=\"inleiding\">Inleiding<\/h2>\n<p>Vaak vragen studenten wat het verschil is tussen de dunders <code>__str__<\/code> en <code>__repr__<\/code> in een Python-klasse. Een begrijpelijke vraag, want intu\u00eftief doen beide methoden hetzelfde: ze geven een string-representatie van het object in kwestie. Dus wat <em>zijn<\/em> inderdaad de overeenkomsten en verschillen?<\/p>\n<p>Er zijn natuurlijk al diverse blog-posts die dit uitleggen. Zo is er een uitgebreide beschrijving op <a href=\"https:\/\/www.journaldev.com\/22460\/python-str-repr-functions\">journaldev.com<\/a>, of <a href=\"https:\/\/stackoverflow.com\/questions\/1436703\/what-is-the-difference-between-str-and-repr\/1436756#1436756\">deze uitleg op StackOverflow<\/a>. Maar het leek me handig om een Nederlandstalige beschrijving te maken, waarbij ook wordt ingegaan op de momenten waarop beide methoden worden aangeroepen.<\/p>\n<h1 id=\"wat-zegt-de-documentatie-\">Wat zegt de documentatie?<\/h1>\n<p>De documentatie op python.org lijkt in eerste instantie duidelijk. De <code>__repr__<\/code>-methode <a href=\"https:\/\/docs.python.org\/3\/reference\/datamodel.html#object.__repr__\">geeft de <em>&#8216;offici\u00eble&#8217; string-represenatie<\/em> van het object terug<\/a>, terwijl de <code>__str__<\/code>-methode <a href=\"https:\/\/docs.python.org\/3\/reference\/datamodel.html#object.__str__\">de <em>&#8216;informele&#8217; of netjes afdrukbare representatie<\/em> van het object terug moet geven<\/a>. Let op dat deze documentatie gebruik maakt van apostrofen om aan te geven dat zij ook niet precies defini\u00ebren wat een &#8216;officie\u00eble&#8217; of &#8216;informele&#8217; is.<\/p>\n<p>Hoewel, dat is niet helemaal waar. De documentatie bij <code>__repr__<\/code>vervolgt met de opmerking dat de string die deze methode teruggeeft een <em>valide Python-expressie<\/em> is die in het ideale geval gebruikt kan worden om het object opnieuw te genereren. In die betekenis is <code>__repr__<\/code> dus een soort <em>serialisatie<\/em> methode.<\/p>\n<h1 id=\"een-verschil-in-aanroepen\">Een verschil in aanroepen<\/h1>\n<p>Laten we eens kijken wanneer de verschillende methoden worden aangeroepen.<\/p>\n<pre><code class=\"lang-ipython\">In [<span class=\"hljs-number\">1<\/span>]: class Demo:\n   ...:     pass\n   ...: \n   ...: d = Demo()\n   ...: print (d)\n   ...: \n   ...: print (d.__repr__())\n   ...: print (d.__str__())\n\n&lt;__main__<span class=\"hljs-selector-class\">.Demo<\/span> <span class=\"hljs-selector-tag\">object<\/span> at <span class=\"hljs-number\">0<\/span>x7f8a1441bf70&gt;\n&lt;__main__<span class=\"hljs-selector-class\">.Demo<\/span> <span class=\"hljs-selector-tag\">object<\/span> at <span class=\"hljs-number\">0<\/span>x7f8a1441bf70&gt;\n&lt;__main__<span class=\"hljs-selector-class\">.Demo<\/span> <span class=\"hljs-selector-tag\">object<\/span> at <span class=\"hljs-number\">0<\/span>x7f8a1441bf70&gt;\n\nIn [<span class=\"hljs-number\">2<\/span>]:\n<\/code><\/pre>\n<p>De output van al deze print-statements is in alle gevallen hetzelfde: <code>'&lt;__main__.Demo object at 0x7fe0adc4d910&gt;'<\/code>. Dat is niet heel zinvolle informatie, en het is ook logisch dat bede methoden dezelfde output geven: de implementatie van <code>__str__<\/code> in <code>Object<\/code> roept gewoon <code>__repr__<\/code> aan.<\/p>\n<p>Wat dan, wanneer we \u00e9\u00e9n van beide methoden in onze klasse implementeren?<\/p>\n<pre><code class=\"lang-ipython\">In [<span class=\"hljs-number\">1<\/span>]: <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Demo<\/span>:<\/span>\n   ...:     <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__init__<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">self<\/span>)<\/span><\/span>:\n   ...:         <span class=\"hljs-keyword\">self<\/span>.demostr = <span class=\"hljs-string\">'hallo allemaal'<\/span>\n   ...: \n   ...:     <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__repr__<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">self<\/span>)<\/span><\/span>:\n   ...:         <span class=\"hljs-keyword\">return<\/span> f<span class=\"hljs-string\">'Demo object met demostr: {self.demostr}'<\/span>\n   ...: \n   ...: d = Demo()\n   ...: print (d)\n\nDemo object met <span class=\"hljs-symbol\">demostr:<\/span> hallo allemaal\n\nIn [<span class=\"hljs-number\">2<\/span>]<span class=\"hljs-symbol\">:<\/span>\n<\/code><\/pre>\n<p>Het resultaat van deze test is de string die door <code>__repr__<\/code> wordt teruggegeven: <code>Demo object met demostr: hallo allemaal<\/code>. Blijkbaar wordt door <code>print<\/code> een call gedaan naar <code>__repr__<\/code>. Maar wat nu als we beide methoden implementen?<\/p>\n<pre><code class=\"lang-ipython\">In [<span class=\"hljs-number\">1<\/span>]: <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Demo<\/span>:<\/span>\n   ...:     <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__init__<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">self<\/span>)<\/span><\/span>:\n   ...:         <span class=\"hljs-keyword\">self<\/span>.demostr = <span class=\"hljs-string\">'hallo allemaal'<\/span>\n   ...: \n   ...:     <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__repr__<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">self<\/span>)<\/span><\/span>:\n   ...:         <span class=\"hljs-keyword\">return<\/span> f<span class=\"hljs-string\">'Demo object met demostr: {self.demostr}'<\/span>\n   ...: \n   ...:     <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__str__<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">self<\/span>)<\/span><\/span>:\n   ...:         <span class=\"hljs-keyword\">return<\/span> f<span class=\"hljs-string\">'De str-methode aangeroepen.'<\/span>\n   ...: \n   ...: \n   ...: d = Demo()\n   ...: print (d)\n\nDe str-methode aangeroepen.\n\nIn [<span class=\"hljs-number\">2<\/span>]<span class=\"hljs-symbol\">:<\/span>\n<\/code><\/pre>\n<p>Dit maakt duidelijk dat er een call naar <code>__str__<\/code> gedaan wordt. Blijkbaar kijkt Python bij een <code>print<\/code> eerst of er een implementatie van <code>__str__<\/code> bestaat; als dat niet het geval is, wordt een poging gedaan <code>__repr__<\/code> aan te roepen, en als die er ook niet is, wordt hetzelfde protocol bij de superklasse toegepast. En zo verder totdat we bij <code>Object<\/code> aankomen.<\/p>\n<h1 id=\"het-automatisch-aanroepen-van-__repr__-\">Het automatisch aanroepen van <code>__repr__<\/code><\/h1>\n<p>Dus <code>print<\/code> roept automatisch <code>__str__<\/code> aan. Maar is er ook een situatie denkbaar waarin automatisch <code>__repr__<\/code> 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 <code>print<\/code>, zoals we vaak doen in de interactieve shell:<\/p>\n<pre><code class=\"lang-ipython\">In [<span class=\"hljs-number\">1<\/span>]: <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Demo<\/span>:<\/span>\n   ...:     <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__init__<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">self<\/span>)<\/span><\/span>:\n   ...:         <span class=\"hljs-keyword\">self<\/span>.demostr = <span class=\"hljs-string\">'hallo allemaal'<\/span>\n   ...: \n   ...:     <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__repr__<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">self<\/span>)<\/span><\/span>:\n   ...:         <span class=\"hljs-keyword\">return<\/span> f<span class=\"hljs-string\">'Demo object met demostr: {self.demostr}'<\/span>\n   ...: \n   ...:     <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__str__<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">self<\/span>)<\/span><\/span>:\n   ...:         <span class=\"hljs-keyword\">return<\/span> f<span class=\"hljs-string\">'De str-methode aangeroepen.'<\/span>\n   ...: \n   ...: \n   ...: d = Demo()\n   ...: print (d)\nDe str-methode aangeroepen.\n\nIn [<span class=\"hljs-number\">2<\/span>]: d\nOut[<span class=\"hljs-number\">2<\/span>]: Demo object met <span class=\"hljs-symbol\">demostr:<\/span> hallo allemaal\n\nIn [<span class=\"hljs-number\">3<\/span>]<span class=\"hljs-symbol\">:<\/span>\n<\/code><\/pre>\n<p>Een andere situatie waarin <code>__repr__<\/code> wordt aangeroepen is wanneer het object een onderdeel is van een <code>tupel<\/code> of een <code>dictionary<\/code> die wordt uitgeprint:<\/p>\n<pre><code class=\"lang-ipython\"><span class=\"hljs-symbol\">In<\/span> [<span class=\"hljs-number\">4<\/span>]: print ([d])\n[<span class=\"hljs-symbol\">Demo<\/span> object met demostr: hallo allemaal]\n\n<span class=\"hljs-symbol\">In<\/span> [<span class=\"hljs-number\">5<\/span>]: print ({<span class=\"hljs-string\">'key'<\/span>:d})\n{<span class=\"hljs-string\">'key'<\/span>: <span class=\"hljs-symbol\">Demo<\/span> object met demostr: hallo allemaal}\n\n<span class=\"hljs-symbol\">In<\/span> [<span class=\"hljs-number\">7<\/span>]:\n<\/code><\/pre>\n<h1 id=\"serialisatie-door-middel-van-__repr__-\">Serialisatie door middel van <code>__repr__<\/code><\/h1>\n<p>Zoals aangegeven, is het de bedoeling van <code>__repr__<\/code> dat deze methode een representatie teruggeeft die in principe gebruikt kan worden om het object te regenereren. Dat betekent dat <code>obj == eval(repr(obj))<\/code> in de regel een waarde zou moeten hebben van <code>True<\/code> (dat kan niet altijd natuurlijk, maar dit is een ideaal). Hoe zou je zoiets kunnen realiseren?<\/p>\n<p>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.<\/p>\n<pre><code class=\"lang-python\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Demo<\/span>:<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__init__<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">self<\/span>, **kwargs)<\/span><\/span>:\n        <span class=\"hljs-keyword\">self<\/span>.__dict_<span class=\"hljs-number\">_<\/span>.update(kwargs) \n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">update<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">self<\/span>, key, value)<\/span><\/span>:\n        <span class=\"hljs-keyword\">self<\/span>.__dict_<span class=\"hljs-number\">_<\/span>[key] = value\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__str__<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">self<\/span>)<\/span><\/span>:\n        <span class=\"hljs-keyword\">return<\/span> f<span class=\"hljs-string\">'Demo object, verder niks aan de hand'<\/span>\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__repr__<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">self<\/span>)<\/span><\/span>:\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">self<\/span>.__dict_<span class=\"hljs-number\">_<\/span>\n\n<span class=\"hljs-comment\"># maak een object aan met wat data<\/span>\nobject1 = Demo(naam=<span class=\"hljs-string\">'Bart'<\/span>, woonplaats=<span class=\"hljs-string\">'Groningen'<\/span>, huisnummer=<span class=\"hljs-number\">116<\/span>)\n<span class=\"hljs-comment\"># verander de interne status van het object<\/span>\nobject1.update(<span class=\"hljs-string\">'huisnummer'<\/span>, <span class=\"hljs-number\">200<\/span>) \n<span class=\"hljs-comment\"># en printen de interne status<\/span>\nprint (object1.__repr_<span class=\"hljs-number\">_<\/span>())\n\n<span class=\"hljs-comment\"># hier maken we een kopie aan van het eerste object<\/span>\nobject2 = Demo(**object1.__repr_<span class=\"hljs-number\">_<\/span>())\n<span class=\"hljs-comment\"># en printen de interne status; die is hetzelfde als object1<\/span>\nprint (object2.__repr_<span class=\"hljs-number\">_<\/span>())\n\n<span class=\"hljs-comment\"># nu passen we de interne status van het eerste object aan <\/span>\nobject2.update(<span class=\"hljs-string\">'huisnummer'<\/span>, <span class=\"hljs-number\">60<\/span>)\n\n<span class=\"hljs-comment\"># en printen van beide objecten de interne status af<\/span>\nprint (object1.__repr_<span class=\"hljs-number\">_<\/span>())\nprint (object2.__repr_<span class=\"hljs-number\">_<\/span>())\n<\/code><\/pre>\n<p>De code hierboven levert het volgende op:<\/p>\n<pre><code class=\"lang-python\">{<span class=\"hljs-string\">'naam'<\/span>: <span class=\"hljs-string\">'Bart'<\/span>, <span class=\"hljs-string\">'woonplaats'<\/span>: <span class=\"hljs-string\">'Groningen'<\/span>, <span class=\"hljs-string\">'huisnummer'<\/span>: <span class=\"hljs-number\">116<\/span>}\n{<span class=\"hljs-string\">'naam'<\/span>: <span class=\"hljs-string\">'Bart'<\/span>, <span class=\"hljs-string\">'woonplaats'<\/span>: <span class=\"hljs-string\">'Groningen'<\/span>, <span class=\"hljs-string\">'huisnummer'<\/span>: <span class=\"hljs-number\">200<\/span>}\n{<span class=\"hljs-string\">'naam'<\/span>: <span class=\"hljs-string\">'Bart'<\/span>, <span class=\"hljs-string\">'woonplaats'<\/span>: <span class=\"hljs-string\">'Groningen'<\/span>, <span class=\"hljs-string\">'huisnummer'<\/span>: <span class=\"hljs-number\">200<\/span>}\n{<span class=\"hljs-string\">'naam'<\/span>: <span class=\"hljs-string\">'Bart'<\/span>, <span class=\"hljs-string\">'woonplaats'<\/span>: <span class=\"hljs-string\">'Groningen'<\/span>, <span class=\"hljs-string\">'huisnummer'<\/span>: <span class=\"hljs-number\">60<\/span>}\n<\/code><\/pre>\n<h1 id=\"conclusie\">Conclusie<\/h1>\n<p>Dus <code>__str__<\/code> en <code>__repr__<\/code> 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 <code>__str__<\/code> te implementeren, en <code>__repr__<\/code> te gebruiken voor klassen die geserialiseerd moeten worden.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Inleiding Vaak vragen studenten wat het verschil is tussen de dunders __str__ en __repr__ in een Python-klasse. Een begrijpelijke vraag, want intu\u00eftief 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<\/p>\n<p class=\"more-link\"><a href=\"https:\/\/www.bartbarnard.nl\/blog\/python-string-representaties\/\" class=\"themebutton2\">Read More<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[19],"tags":[42,43],"_links":{"self":[{"href":"https:\/\/www.bartbarnard.nl\/blog\/wp-json\/wp\/v2\/posts\/1086"}],"collection":[{"href":"https:\/\/www.bartbarnard.nl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.bartbarnard.nl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.bartbarnard.nl\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.bartbarnard.nl\/blog\/wp-json\/wp\/v2\/comments?post=1086"}],"version-history":[{"count":2,"href":"https:\/\/www.bartbarnard.nl\/blog\/wp-json\/wp\/v2\/posts\/1086\/revisions"}],"predecessor-version":[{"id":1088,"href":"https:\/\/www.bartbarnard.nl\/blog\/wp-json\/wp\/v2\/posts\/1086\/revisions\/1088"}],"wp:attachment":[{"href":"https:\/\/www.bartbarnard.nl\/blog\/wp-json\/wp\/v2\/media?parent=1086"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.bartbarnard.nl\/blog\/wp-json\/wp\/v2\/categories?post=1086"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.bartbarnard.nl\/blog\/wp-json\/wp\/v2\/tags?post=1086"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}