CS61A(18): Representation

This is the lecture note of CS61A - Lecture 18.

String Representations

An object value should behave like the kind of data it is meant to represent.

In Python, all objects produce two string representations:

  • The str is legible to humans
  • The repr is legible to the Python interpreter

🌋 The print() function calls the built-in __str__ method of the object, while simply calling the object in interactive mode calls the built-in __repr__ method.

The str and repr strings are often the same, but not always.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# Demo

>>> from fractions import Fraction
>>>
>>> half = Fraction(1, 2)
>>> half # repr string
Fraction(1, 2)
>>> print(half) # str string
1/2
>>> repr(half)
'Fraction(1, 2)'
>>> str(half)
'1/2'
>>> eval(repr(half))
Fraction(1, 2)
>>> eval(str(half))
0.5


>>> s = "Hello, World"
>>> s
'Hello, World'
>>> print(repr(s))
'Hello, World'
>>> print(s)
Hello, World
>>> print(str(s))
Hello, World
>>> repr(s)
"'Hello, World'"
>>> repr(repr(repr(s)))
'\'"\\\'Hello, World\\\'"\''
>>> str(s)
'Hello, World'
>>>
>>> eval(repr(s))
'Hello, World'
>>> eval(str(s)) # not a valid expression
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
NameError: name 'Hello' is not defined
>>> eval(s)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
NameError: name 'Hello' is not defined

String Interpolation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Demo

>>> f'2 + 2 = {2 + 2}'
'2 + 2 = 4'
>>> f'2 + 2 = {(lambda x: 2 * x)(2)}'
'2 + 2 = 4'


>>> from fractions import Fraction
>>>
>>> half = Fraction(1, 2)
>>> f'half of a half is {half * half}' # str string
'half of a half is 1/4'
>>>
>>> f'half of a half is {repr(half * half)}' # repr string
'half of a half is Fraction(1, 4)'

Polymorphic Functions

Let's see some examples.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Bear:
"""A bear."""

def __repr__(self):
return 'Bear()'

oski = Bear()
print(oski) # Bear() "same as print(str(oski))"
print(str(oski)) # Bear() "no class attribute __str__, so use class attribute __repr__"
print(repr(oksi)) # Bear() "use class attribute __repr__"
print(oski.__str__()) # Bear() "no instance attribute __str__, so use class attribute __str__"
print(oski.__repr__()) # Bear() "no instance attribute __repr__, so use class attribute __repr__"

"""
Since there is no __str__() method, so the __repr__() will be used.
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Bear:
"""A bear."""

def __repr__(self):
return 'Bear()'

def __str__(self):
return 'a bear'

oski = Bear()
print(oski) # a bear "same as print(str(oski))"
print(str(oski)) # a bear "so use class attribute __str__"
print(repr(oksi)) # Bear() "use class attribute __repr__"
print(oski.__str__()) # a bear "no instance attribute __str__, so use class attribute __str__"
print(oski.__repr__()) # Bear() "no instance attribute __repr__, so use class attribute __repr__"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Bear:
"""A bear."""

def __init__(self):
self.__repr__ = lambda: 'oski'
self.__str__ = lambda: 'this bear'

def __repr__(self):
return 'Bear()'

def __str__(self):
return 'a bear'

oski = Bear()
print(oski) # a bear "same as print(str(oski))"
print(str(oski)) # a bear "ignore instance attribute and just use class attribute __str__"
print(repr(oski)) # Bear() "ignore instance attribute and just use class attribute __repr__"
print(oski.__str__()) # this bear "attribute lookup: use instance attribute __str__"
print(oski.__repr__()) # oski "attribute lookup: use instance attribute __repr__"

To check our understanding, let's try to implement our repr and str function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Bear:
"""A bear."""

def __init__(self):
self.__repr__ = lambda: 'oski'
self.__str__ = lambda: 'this bear'

def __repr__(self):
return 'Bear()'

def __str__(self):
return 'a bear'

# replace the built-in functions

def repr(x):
return type(x).__repr__(x)

def str(x):
t = type(x)
if hasattr(t, '__str__'):
return t.__str__(x)
else:
return repr(x)

oski = Bear()
print(oski) # a bear "same as print(str(oski))"
print(str(oski)) # a bear "call our defined str() function, since class Bear has attribute __str__, so execute code at line 11"
print(repr(oski)) # Bear() "call our defined repr() function, execute code at 8"
print(oski.__str__()) # this bear "attribute lookup: code at line 6"
print(oski.__repr__()) # oski "attribute lookup: code at line 5"

Let's see a demo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Ratio:
def __init__(self, n, d):
self.numer = n
self.denom = d

def __repr__(self):
return 'Ratio({0}, {1})'.format(self.numer, self.denom)

def __str__(self):
return '{0}/{1}'

>>> half = Ratio(1, 2)
>>> print(half)
1/2
>>> half
'Ratio(1, 2)'

Special Method Names

Special method names are a topic particularly to the Python.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Ratio:
def __init__(self, n, d):
self.numer = n
self.denom = d

def __repr__(self):
return 'Ratio({0}, {1})'.format(self.numer, self.denom)

def __str__(self):
return '{0}/{1}'{% asset_img 20-5.JPG %}

# Generic
def __add__(self, other):
if isinstance(other, int):
n = self.numer + self.denom * other
d = self.denom
elif isinstance(other, Ratio):
n = self.numer * other.denom + self.denom * other.numer
d = self.denom * other.denom
elif isinstance(other, float):
return float(self) + other
g = gcd(n, d)
return Radio(n//g, d//g)

__radd__ = __add__

def __float__(self):
return self.numer / self.denom

def gcd(n, d):
while n != d:
n, d = min(n, d), abs(n-d)
return n

🏝️ Supplement Link: What is the meaning of single and double underscore