emulate the behavior of built-in types. For example, to get the length of a string you can call len('string'). But an empty class definition doesn’t support this behavior out of the box:
class NoLenSupport:
pass
>>> obj = NoLenSupport()
>>> len(obj)
TypeError: "object of type 'NoLenSupport' has no len()"
- To fix this, you can add a len dunder method to your class:
class LenSupport:
def __len__(self):
return 42
>>> obj = LenSupport()
>>> len(obj)
42
- Object Initialization: init
class Account:
"""A simple account class"""
def __init__(self, owner, amount=0):
"""
This is the constructor that lets us create
objects from this class
"""
self.owner = owner
- Object Representation: str, repr
-
repr: The “official” string representation of an object. This is how you would make an object of the class. The goal of repr is to be unambiguous.
-
str: The “informal” or nicely printable string representation of an object. This is for the enduser.
class Account:
# ... (see above)
def __repr__(self):
return 'Account({!r}, {!r})'.format(self.owner, self.amount)
def __str__(self):
return 'Account of {} with starting amount: {}'.format(
self.owner, self.amount)
- Iteration: len, getitem, reversed
def add_transaction(self, amount):
if not isinstance(amount, int):
raise ValueError('please use int for amount')
self._transactions.append(amount)
- Operator Overloading for Comparing Accounts: eq, lt functools.total_ordering decorator which allows me to take a shortcut, only implementing eq and lt:
from functools import total_ordering
@total_ordering
class Account:
# ... (see above)
def __eq__(self, other):
return self.balance == other.balance
def __lt__(self, other):
return self.balance < other.balance
- Operator Overloading for Merging Accounts: add The expected behavior would be to merge all attributes together
def __add__(self, other):
owner = self.owner + other.owner
start_amount = self.balance + other.balance
return Account(owner, start_amount)
-
Callable Python Objects: call the downside of having a call method on your objects is that it can be hard to see what the purpose of calling the object is.
-
Context Manager Support and the With Statement: enter, exit
is a simple “protocol” (or interface) that your object needs to follow so it can be used with the with statement. Basically all you need to do is add enter and exit methods to an object if you want it to function as a context manager.
- Python Iterators That Iterate Forever We’ll begin by writing a class that demonstrates the bare-bones iterator protocol in Python. implement a class called Repeater that can be iterated over with a for-in loop, like so:
repeater = Repeater('Hello')
for item in repeater:
print(item)
- How do for-in loops work in Python? the for-in was just syntactic sugar for a simple while loop:
repeater = Repeater('Hello')
iterator = repeater.__iter__()
while True:
item = iterator.__next__()
print(item)
- It first prepared the repeater object for iteration by calling its iter method. This returned the actual iterator object.
- After that, the loop repeatedly calls the iterator object’s next method to retrieve values from it.
what the class looked like in its second (simplified) version:
class Repeater:
def __init__(self, value):
self.value = value
def __iter__(self):
return self
def __next__(self):
return self.value
If I rewrite this iterator class as a generator, it looks like this:
def repeater(value):
while True:
yield value
- The yield statement allows you to temporarily suspend execution of a generator function and to pass back values from it.
- Generators start raising StopIteration exceptions after control flow leaves the generator function by any means other than a yield statement.