Representation

String Interpolation #

This is not part of representation, but is instead an extremely useful tool to make writing strings with multiple variables far cleaner. You may have already seen me use string interpolation earlier on in the course.

In Python, the cleanest way to use string interpolation is with an f-string, where the letter f is appended before quotation marks.

f""

You can see the syntax highlighting sees the f in a different colour! With this notation, we can then put expressions in our string itself and have them evaluate to ‘proper’ values. Different languages deal with string interpolation in different manners.

one = 1
two = "two"
five = "five"
f"{one + 1} {two}, {five}" # will return "2 two, five"

Representation #

Built-in Object Attributes #

In Python, every built in type inherits from object. Everything in Python is an object of some sort. As a result, they all have their own dunder methods that are different for every object.

If you run dir() on an object (which could be a string, list, or something else), you will see a list of its methods. Behind the scenes, Python runs these dunder methods, so we don’t really need to worry about those (kind of like data abstraction if you think about it)

String Representation #

The __str__ method returns a string representation made to be human readable.

The use of this method is most likely better with a concrete example, so let’s define our own class for Rational numbers (similarly to the data abstraction we were using earlier).

from math import gcd

class Rational:
    def __init__(self, numerator: int, denominator: int): 
        g = gcd(numerator, denominator)
        self.numerator = numerator // g
        self.denominator = denominator // g

So far, if we make an instance of the class and then print that instance, we get a non-useful output:

>>> my_rational = Rational(2, 3)
>>> print(my_rational)
<__main__.Rational object>

When we define our own __str__ method, calling print() on the instance will look for the __str__ method and return the value. However, the print method removes quotes, while calling str() on the same thing will not remove quotes (an example will be given in the doctests).

class Rational:
    """
    >>> my_rational = Rational(2, 3)
    >>> print(my_rational)
    2 / 3
    >>> str(my_rational)
    '2 / 3'
    """
    def __init__(self, numerator: int, denominator: int): 
        g = gcd(numerator, denominator)
        self.numerator = numerator // g
        self.denominator = denominator // g

    def __str__(self):
        return f"{self.numerator} / {self.denominator}" # We define how our class will look in the console here

Machine Representation #

On the other hand, the __repr__ method is used to return a string that would evaluate to an object with the same values (in a traditional sense - when you override the __repr__ method, you can set it to anything you want)

The goal is to make it such that when you call eval() on the result, it should return the same valued object (but not the same pointer).

class Rational:
    """
    >>> my_rational = Rational(2, 3)
    >>> my_rational
    Rational(2, 3)
    >>> eval(Rational.__repr__(my_rational))
    Rational(2, 3)
    >>> repr(my_rational)
    'Rational(2, 3)'
    """
    def __init__(self, numerator: int, denominator: int): 
        g = gcd(numerator, denominator)
        self.numerator = numerator // g
        self.denominator = denominator // g

    def __str__(self):
        return f"{self.numerator} / {self.denominator}"

    def __repr__(self):
        return f"Rational({self.numerator}, {self.denominator})"

Notice how calling repr(my_rational) returned what you would expect but in quotation marks.

Implicit calling of print() #

Type Methods of Calling
Implicitly calls print() Directly calling in interactive environment; print()
Does not implicitly call print() repr(), str()

Doing print("Ben") to the console will output Ben (without the quote marks). In other words, calling print() on something removes a set of quote marks. However, calling str("Ben") in the console will output 'Ben' instead, suggesting that it doesn’t call print() to remove the set of quotes. If we directly output something to the console, it first calls repr() on it, then prints it to the console. So overall, when we just call something, it does the equivalent of print(repr("Ben")), which is the same as print("'Ben'"), which will remove the outside set of quotes and then return 'Ben' to the console.


print("Ben") # Ben
"Ben" # 'Ben' (comes from print(repr("Ben")))

repr("Ben") # "'Ben'"
str("Ben") # 'Ben'

Special Methods #

There are other special dunder methods that map to built-in behaviour. For example, the __add__ method is called when two objects are added together:

2 + 3 # is the same as 2.__add__(3)

If we wanted to add two of our Rational classes together, it would error because there is currently no __add__ method defined. However, if we were to define our own __add__ method, we could make it such that Python would know how to deal with addition!

class Rational:
    def __init__(self, numerator: int, denominator: int): 
        g = gcd(numerator, denominator)
        self.numerator = numerator // g
        self.denominator = denominator // g

    def __add__(self, other):
        assert isinstance(other, Rational) # Will require that the other thing passed in was a rational

        new_numerator = self.numerator * other.denominator + other.numerator * self.denominator
        new_denominator = self.denominator * other.denominator
        return Rational(new_numerator, new_denominator)

    def __str__(self):
        return f"{self.numerator} / {self.denominator}"

    def __repr__(self):
        return f"Rational({self.numerator}, {self.denominator})"

This can be done for other methods like __mul__ - you just have to implement them yourselves.

Weird behaviour of str and repr #

repr() #

  • Will ignore any instance variables created called __repr__. Only looks for this instance variable in the class.

str() #

  • Will ignore any instance variables created called __str__. Only looks for this instance variable in the class.
  • If no __str__ is found (in the whole lookup order), it defaults to the first __repr__ it can find.