Dunder Methods
Dunder Methods
Let’s talk about this __ thing we keep seeing around method names.
We call these methods “dunder” methods which stands for “double under” or “double underscore”.
Python uses an assortment of dunder methods to allow for operator overloading and implementing various other protocols. These methods start and end with two underscores.
In real life, we never need to call these methods ourselves. They are only used for customizing behavior of other functions and operators. We show calling them here so you can see what they do and why we implement them.
These are sometimes called “magic methods” but they are not magical. You can define dunder methods to customize the behavior of your own classes.
Review
We have already seen some dunder methods. Let’s review.
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
def __str__(self):
return f"Account with balance of ${self.balance}"
def __repr__(self):
return f"BankAccount(balance={self.balance})"
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
self.balance -= amount
Our BankAccount class had:
an
__init__method that is called to initialize the classan
__str__method that is called when converting the object to a human-readable stringa
__repr__method that is called when converting the object to a developer-readable string
More Duck Typing
We briefly talked about “duck typing” earlier.
Duck typing means that we pretty much never care about the types of objects. Instead of checking what the type of an object is, we instead check whether it implements a feature.
Dunder methods provide a way for our class to customize operators and other built-in Python behavior for our objects.
Operator Overloading
>>> "Hello " + "World"
'Hello World'
>>> "Hello " * 3
'Hello Hello Hello '
>>> "Hello " - "H"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'str' and 'str'
When we refer to “operator overloading” we really just mean implementing a particular operator’s behavior on our object.
Strings are overloaded for + and * but not -.
Arithmetic Operators
>>> a = {1, 2}
>>> b = {2, 3}
>>> a.__sub__(b)
{1}
>>> a - b
{1}
>>> "hello ".__add__("world")
'hello world'
>>> "hello " + "world"
'hello world'
>>> x = 3
>>> y = 2
>>> x.__mul__(y)
6
>>> x * y
6
Comparison Operators
>>> x = 2
>>> y = 5
>>> x.__lt__(y)
True
>>> x < y
True
>>> x.__eq__(y)
False
>>> x == y
False
>>> x.__gt__(y)
False
>>> x > y
False
>>> x.__ne__(y)
True
>>> x != y
True
>>> x.__ge__(y)
False
>>> x >= y
False
>>> x.__le__(y)
True
>>> x <= y
True
Truthiness
How do we determine the truthiness of an object?
>>> x = 2
>>> y = 0
>>> bool(x)
True
>>> bool(y)
False
Truthiness is also something we can customize with dunder methods:
>>> x.__bool__()
True
>>> y.__bool__()
False
Note
bool checks for a non-zero return value from __len__ if no __bool__ method is available.
Callable
We can use the __call__ method to make a class instance callable:
class NameSayer:
def __init__(self, name):
self.name = name
def __call__(self):
print(f"Hi {self.name}")
We can call our class instance just like any other function:
>>> say_trey = NameSayer("Trey")
>>> say_trey
<NameSayer object at 0x...>
>>> type(say_trey)
<class 'NameSayer'>
>>> say_trey()
Hi Trey
Dunder Exercises
Pythonic Points
Edit the Point class in the properties.py file to:
Support using the
+operator between two pointsSupport using the
*operator between two a point and a numberSupport the
==and!=operators between two points
Note
To test these changes, you should modify the PointTests class in properties_test.py to comment out the @unittest.skip for “Pythonic Points”.
>>> p1 = Point(1, 2, 3)
>>> p2 = Point(4, 5, 6)
>>> p1 + p2
Point(5, 7, 9)
>>> p1 * 2
Point(2, 4, 6)
>>> p1 == p2
False
>>> p1 + Point(3, 3, 3) == p2
True
Truthy Account
Edit the BankAccount class in the classes.py file to make account objects truthy when the bank account has money in it.
Note
To test these changes, you should modify the BankAccountTests class in classes_test.py to comment out the lines with “Comment this line for truthy accounts exercise.”
Example usage:
>>> from classes import BankAccount
>>> trey_account = BankAccount(100)
>>> bool(trey_account)
True
>>> trey_account.withdraw(100)
>>> bool(trey_account)
False
>>> trey_account.withdraw(1)
>>> bool(trey_account)
False
You can use this class:
class BankAccount:
"""Bank account that supports truthiness."""
def __init__(self, balance=0):
self.balance = balance
def __repr__(self):
return f"{type(self).__name__}(balance={self.balance})"
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
self.balance -= amount
def transfer(self, other_account, amount):
self.withdraw(amount)
other_account.deposit(amount)
Hint
Use the __bool__ method
Comparable Account
Edit the BankAccount class in the classes.py file to allow accounts to be compared using arithmetic comparison operators.
Note
To test these changes, you should modify the BankAccountTests class in classes_test.py to comment out the line with “Comment this line for BankAccount comparison exercise.”
Example:
>>> from classes import BankAccount
>>> account1 = BankAccount(balance=100)
>>> account2 = BankAccount(balance=200)
>>> account1 == account2
False
>>> account1 < account2
True
>>> account1 >= account2
False
Caseless String
This is the CaselessString exercise in dunder.py.
Use UserString to make a CaselessString class which does comparisons in a case insensitive manner.
Example:
>>> from dunder import CaselessString
>>> s1 = CaselessString("Hello there")
>>> s2 = CaselessString("hello there")
>>> s3 = CaselessString("HELLO THERE")
>>> s1 == s2 == s3
True
>>> s1.lower() == s2.upper()
True
Easy Dict
Edit the EasyDict class in the dunder.py file to make an EasyDict class that can be used with both attribute and item syntax.
Run python test.py EasyDict in your exercises directory.
Example:
>>> from dunder import EasyDict
>>> a = EasyDict()
>>> a['shoe'] = "blue"
>>> a.shoe
"blue"
>>> a['shoe']
"blue"
>>> a.car = "green"
>>> a['car']
"green"
Hint
Use the __getitem__ and __setitem__ methods
Cyclic List
This is the CyclicList exercise in dunder.py.
Create a list-like data structure that loops in a cyclic manner:
>>> from dunder import CyclicList
>>> numbers = CyclicList([1, 2, 3, 4])
>>> numbers[1]
2
>>> numbers[2]
3
>>> numbers[12]
1
>>> [x for x, _ in zip(numbers, range(10))]
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2]
>>> numbers[35]
4