Lecture 7: Objects and Classes Continued: Magic Methods

Eric Brauer

Review: Why Use Objects?

  • Allows us to better organise code
  • Combine relevant attributes and methods together
  • Keep attributes private, only expose what another developer might need
  • Lets us keep things simple!

“In Python, Everything Is An Object”

Integers can be printed (even though they are not strings), and they can be compared to floats (even though they are different datatypes). This is intuitive behaviour, it makes sense to us even though it shouldn’t be possible.

#!/usr/bin/env python3
x=3
>>>> print(x)
3
>>>> 3 > 2.9
True

Object Behaviour

How objects behave is defined in their help documentation. Try looking at this now.

>>>> help(int)

Running the builtin help() will show you all the methods that belong to integer objects.

What are Magic Methods?

Notice all the methods that start & end with __? These are dunder methods (“double underline”). Otherwise called magic methods.

We don’t call these methods directly by name.

Rather these are called when we use the object with different operators or builtin functions.

Equal To Operator

>>>> x = 3
>>>> x == 4.0
False

When we use the integer object with a == symbol, we are actually calling the __eq__ method, which will return a True or False.

__eq__

def __eq__(self, other):
    return self==other

For operators, “self” will refer to the left side of the equation, and “other” will refer to the right.

Magic Methods

Every built-in object in Python will be implementing a lot of these magic methods. This helps us because it means that objects in Python will behave in predictable ways. By implementing magic methods in our classes, we can allow the user to use our objects in intuitive ways.

A Quick Example

Let’s say we want to create a new object for storing temperature. Most countries measure temperature in Celsius, but we might also want to accommodate people who measure in Fahrenheit.

temp1 = Temperature(28, 'C')
temp2 = Temperature(82.4, 'F')
if temp1 == temp2:
    print('It is the same!')

Temperature Class: Named Methods

Class Temperature:

    def __init__(self, temp, unit):
        self.temp = temp
        self.unit = unit

    def rtrn_celsius(self):
        if self.unit == 'F':
            return self.temp / 1.8 - 32
        else:
            return self.temp 

    def rtrn_fahrenheit(self):
        if self.unit == 'C':
            return self.temp * 1.8 + 32  # equation to convert C to F.
        else:
            return self.temp

    def return_unit(self):
        return self.unit

Implementing Dunder Method for ==

It might be more intuitive when comparing temperatures if we let our == symbol ‘detect’ the unit of the other temperature.

    def __eq__(self, other):
        if other.return_unit() == 'C':
            return self.rtrn_celsius() == other.rtrn_celsius()
        elif other.return_unit() == 'F':
            return self.rtrn_fahrenheit() == other.rtrn_fahrenheit()

Programmers would rather use the operators they already know about, than have to read the documentation and discover all of your named methods!

Printing Objects

Let’s create a timer object. Timer is going to expect minutes and seconds. In other words, this is setting up a timer for 02:00.

>>>> t1 = Timer(2, 0)
>>>> print(t1)
<Timer object at 0x7f24b9419940>

This output isn’t very useful to the programmer. We need to define the behaviour for when our object is used with a builtin function, in this case the print() function.

Using str

Class Timer:

    def __init__(self, mins, secs):
        self.mins = mins
        self.secs = secs

    def __str__(self):  # gets called inside print() or str()
        return f"{self.mins:02d}:{self.secs:02d}"

F-Strings

f-strings are a more advanced method of printing. They don’t need manual conversion! Simply put an ‘f’ outside of your quotes, and then variable names inside of curly braces.

user = 'Chris'
pi = 3.14
print(f'Hello World!')  # Hello World!
print(f'Hello {user}, how are you?')  # Hello Chris, how are you?
print(f'The value of pi is {pi}.')  # The value of pi is 3.14.

F-String Formatting

  • f-strings also give you options to format your variables. We can’t cover everything here, but :02d will convert to a 2 digit decimal number with leading zeros, eg. 02.
  • Another useful format is specifying the number of decimals, for example for currency:
x = 3
print(f'$ {x:.2f}')  # format as float, with 2 decimals after the dot.

str VS. repr

repr is used inside the Python interpreter. It will return a representation of the object, when you call the object outside of print() or str().

>>>> t = Timer(2, 0)
>>>> t  # calls __repr__
<template.Timer object at 0x7f199cb4f820>
>>>> print(t)  # calls __str__
02:00

If you define only __repr__, it will also be used for __str__, so it’s better to just write __repr__ (unless you want different outputs!)

List of Magic Methods

Magic Methods will define the behaviour of:

  • Comparison Operators == != < > <= >=
  • Math Operators + - * / ** //
  • Representation and print functions: print(), str()
  • Other builtin functions: len()
  • Slicing: card_deck[0:4] # calls get_item
  • And more…..