import time
class Timer:
def __init__(self):
self.started_at = time.monotonic()
def elapsed(self) -> float:
return time.monotonic() - self.started_at
timer = Timer()
time.sleep(1)
print(f"Took {timer.elapsed():.3f} seconds")
Constructor: __init__ — a function that initialises an object
Timer() calls the constructor and returns a new object
self is always the first argument and refers to the object
Method: elapsed — a function available for each object of this class
Property: self.started_at, also available as timer.started_at from outside
Bonus fact A method is actually a real function accepting self as an argument: one can call Timer.elapsed(timer) instead of timer.elapsed() But please don’t do it!
Bonus question What’s the difference between time.time() and time.monotonic()?
from dataclasses import dataclass
@dataclass
class Song:
title: str
artist: str
duration_seconds: int
song = Song("Bohemian Rhapsody", "Queen", 354)
class Playlist:
def __init__(self, name: str):
self.name = name
self.songs = []
def add(self, song: Song):
self.songs.append(song)
def duration_seconds(self) -> int:
return sum(song.duration_seconds for song in self.songs)
playlist = Playlist("Road Trip")
playlist.add(Song("Bohemian Rhapsody", "Queen", 354))
playlist.add(Song("Take On Me", "a-ha", 225))
playlist.add(Song("Smells Like Teen Spirit", "Nirvana", 301))
print(playlist.duration_seconds())
Methods can accept arguments besides self, just like regular functions
playlist = Playlist("Road Trip")
print(playlist)
class Playlist:
def __init__(self, name: str):
self.name = name
self.songs = []
def add(self, song: Song):
self.songs.append(song)
def duration_seconds(self) -> int:
return sum(song.duration_seconds for song in self.songs)
def __str__(self):
return f"Playlist \"{self.name}\" with {len(self.songs)} songs and a duration of {self.duration_seconds()} seconds"
playlist = Playlist("Road Trip")
playlist.add(Song("Bohemian Rhapsody", "Queen", 354))
playlist.add(Song("Take On Me", "a-ha", 225))
playlist.add(Song("Smells Like Teen Spirit", "Nirvana", 301))
print(playlist)
__len__ — enables len(obj)__bool__ — controls truthiness in if obj__eq__, __lt__, __le__, __gt__, __ge__ — comparisons__hash__ — enables use as a dictionary key or set member__contains__ — enables item in obj__call__ — enables obj()__add__, __sub__, __mul__, and friends — arithmetic operatorsclass BankAccount:
def __init__(self, owner: str, balance: int = 0):
self.owner = owner
self.balance = balance
def deposit(self, amount: int):
self.balance += amount
def withdraw(self, amount: int):
if amount > self.balance:
print(f"Not enough money to withdraw {amount}: only {self.balance} is available")
else:
self.balance -= amount
def __str__(self):
return f"Account {self.owner}: {self.balance}"
account = BankAccount("Andrey")
account.deposit(100)
account.withdraw(200)
account = BankAccount("Andrey")
account.balance = 500000
print(account)
Nobody prevents us from modifying the internal structure of an object. How can we protect ourselves?
class BankAccount:
def __init__(self, owner: str, balance: int = 0):
self._owner = owner
self._balance = balance
What happens?
account = BankAccount("Andrey")
account._balance = 500000
print(account)
class BankAccount:
def __init__(self, owner: str, balance: int = 0):
self.__owner = owner
self.__balance = balance
account = BankAccount("Andrey")
account.__balance = 500000
print(account)
account._BankAccount__balance = 500000
print(account)
class CreditAccount(BankAccount):
def __init__(self, owner: str, balance: int = 0, credit_limit: int = 1000):
super().__init__(owner, balance)
self._credit_limit = credit_limit
def withdraw(self, amount: int):
if amount > self._balance + self._credit_limit:
print(f"Credit limit exceeded for {self._owner}: {amount} requested, {self._balance} balance, {self._credit_limit} limit")
else:
self._balance -= amount
account = CreditAccount("Taylor Swift", 0, 1000)
account.withdraw(500)
print(account)
accounts = [
BankAccount("Taylor Swift", 54000),
CreditAccount("Ed Sheeran", -250, 200),
BankAccount("Beyoncé", 100),
CreditAccount("Bad Bunny", 1250, 3000),
]
for account in accounts:
account.withdraw(500)
print(account)