String representations of class instances: str and repr methods

String representations of class instances: str and repr methods in python can be done by following some methods. Learn more about it here.

String representations of class instances: Motivation

So you’ve just created your first class in Python, a neat little class that encapsulates a playing card:

class Card:
def init(self, suit, pips):
self.suit = suit
self.pips = pips

Elsewhere in your code, you create a few instances of this class:

ace_of_spades = Card('Spades', 1)
four_of_clubs = Card('Clubs', 4)
six_of_hearts = Card('Hearts', 6)

You’ve even created a list of cards, in order to represent a “hand”:

my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]

Now, during debugging, you want to see what your hand looks like, so you do what comes naturally and write:

print(my_hand)

But what you get back is a bunch of gibberish:

[<main.Card instance at 0x0000000002533788>,
<main.Card instance at 0x00000000025B95C8>,
<main.Card instance at 0x00000000025FF508>]

Confused, you try just printing a single card:

print(ace_of_spades)

And again, you get this weird output:

<main.Card instance at 0x0000000002533788>

Have no fear. We’re about to fix this.

First, however, it’s important to understand what’s going on here. When you wrote print(ace_of_spades) you told Python you wanted it to print information about the Card instance your code is calling ace_of_spades. And to be fair, it did.

That output is comprised of two important bits: the type of the object and the object’s id. The second part alone (the hexadecimal number) is enough to uniquely identify the object at the time of the print call.[1]

What really went on was that you asked Python to “put into words” the essence of that object and then display it to you. A more explicit version of the same machinery might be:

string_of_card = str(ace_of_spades)
print(string_of_card)

In the first line, you try to turn your Card instance into a string, and in the second you display it.

The Problem

The issue you’re encountering arises due to the fact that, while you told Python everything it needed to know about the Card class for you to create cards, you didn’t tell it how you wanted Card instances to be converted to strings.

And since it didn’t know, when you (implicitly) wrote str(ace_of_spades), it gave you what you saw, a generic representation of the Card instance.

The Solution (Part 1)

But we can tell Python how we want instances of our custom classes to be converted to strings. And the way we do this is with the str “dunder” (for double-underscore) or “magic” method.

Whenever you tell Python to create a string from a class instance, it will look for a str method on the class, and call it.

Consider the following, updated version of our Card class:

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)

Here, we’ve now defined the str method on our Card class which, after a simple dictionary lookup for face cards, returns a string formatted however we decide.

(Note that “returns” is in bold here, to stress the importance of returning a string, and not simply printing it.

Printing it may seem to work, but then you’d have the card printed when you did something like

str(ace_of_spades), without even having a print function call in your main program. So to be clear, make sure that

str returns a string.).

The str method is a method, so the first argument will be self and it should neither accept, nor be passed additional arguments.

Returning to our problem of displaying the card in a more user-friendly manner, if we again run:

ace_of_spades = Card('Spades', 1)
print(ace_of_spades)

We’ll see that our output is much better:

Ace of Spades

So great, we’re done, right?

Well just to cover our bases, let’s double check that we’ve solved the first issue we encountered, printing the list of Card instances, the hand.

So we re-check the following code:

my_hand = [ace_of_spades, four_of_clubs, six_of_hearts] print(my_hand)

And, to our surprise, we get those funny hex codes again:

[<main.Card instance at 0x00000000026F95C8>,
<main.Card instance at 0x000000000273F4C8>,
<main.Card instance at 0x0000000002732E08>]

What’s going on? We told Python how we wanted our Card instances to be displayed, why did it apparently seem to forget?

The Solution (Part 2)

Well, the behind-the-scenes machinery is a bit different when Python wants to get the string representation of items in a list. It turns out, Python doesn’t care about str for this purpose.

Instead, it looks for a different method, repr, and if that’s not found, it falls back on the “hexadecimal thing”.[2]

So you’re saying I have to make two methods to do the same thing? One for when I want to print my card by itself and another when it’s in some sort of container?

No, but first let’s look at what our class would be like if we were to implement both str and repr methods:

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)
Here, the implementation of the two methods str and repr are exactly the same, except that, to differentiate between the two methods, (S) is added to strings returned by str and (R) is added to strings returned by repr.

Note that just like our str method, repr accepts no arguments and returns a string.

We can see now what method is responsible for each case:

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]

GoalKicker.com – Python® Notes for Professionals 473

print(my_hand) # [Ace of Spades (R), 4 of Clubs (R), 6 of Hearts (R)]
print(ace_of_spades) # Ace of Spades (S)

As was covered, the str method was called when we passed our Card instance to print and the repr method was called when we passed a list of our instances to print.

At this point it’s worth pointing out that just as we can explicitly create a string from a custom class instance using str() as we did earlier, we can also explicitly create a string representation of our class with a built-in function called repr().

String representations of class instances: For example:

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)
And additionally, if defined, we could call the methods directly (although it seems a bit unclear and unnecessary):
print(four_of_clubs.str()) # 4 of Clubs (S)
print(four_of_clubs.repr()) # 4 of Clubs (R)

About those duplicated functions…

Python developers realized, in the case you wanted identical strings to be returned from str() and repr() you might have to functionally-duplicate methods — something nobody likes.

So instead, there is a mechanism in place to eliminate the need for that. One I snuck you past up to this point. It turns out that if a class implements the repr method but not the str method, and you pass an instance of that class to str() (whether implicitly or explicitly), Python will fallback on your repr implementation and use that.

So, to be clear, consider the following version of the Card class:

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)

Note this version only implements the repr method. Nonetheless, calls to str() result in the user-friendly version:

print(six_of_hearts)    #   6   of Hearts   (implicit conversion)
print(str(six_of_hearts))   #   6   of  Hearts  (explicit conversion)

           as do calls to        :                           
repr()                              

print([six_of_hearts])  #[6 of  Hearts] (implicit conversion)

print(repr(six_of_hearts)) # 6 of Hearts (explicit conversion)

String representations of class instances: summary

In order for you to empower your class instances to “show themselves” in user-friendly ways, you’ll want to consider implementing at least your class’s repr method. If memory serves, during a talk Raymond Hettinger said that ensuring classes implement repr is one of the first things he looks for while doing Python code reviews, and by now it should be clear why. The amount of information you could have added to debugging statements, crash reports, or log files with a simple method is overwhelming when compared to the paltry, and often less-than-helpful (type, id) information that is given by default.

If you want different representations for when, for example, inside a container, you’ll want to implement both repr and str methods. (More on how you might use these two methods differently below).

Both methods implemented, 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)

Learn more

LEAVE A REPLY

Please enter your comment!
Please enter your name here