Class : template or the blueprint, build a class to define shared behavior¶
Object :what is created using that template, create objects that use those behaviors¶
a class is like a blueprint or template you use to create objects with.
class ClassName:
# ⇑
# PascalCase convention → common in Python to use when naming classes.
def __init__(self, name, age):
self.name = name # attributes the objects will have
self.age = age # attributes the objects will have
def sample_method(self): # method each object created can call.
print(self.name.upper()) # what the sample_method method will doAttributes : variables within a class, used to store data
Methods : Func. defined within the class
the first parameter of __init__ is always a reference to the specific object being created or used → self → access the object’s own attributes and methods.
instead of
selfwe can use other name too.
__init__ → initializer method
Example:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print(f"{self.name.upper()} says woof woof!")basic syntax for creating objects from a class:
object_1 = ClassName(attribute_1, attribute_2)
object_2 = ClassName(attribute_1, attribute_2)You can also call any of the methods defined in the class from each object:
object_1.method_name()
object_2.method_name()class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print(f"{self.name.upper()} says woof woof! I'm {self.age} years old!")
dog_1 = Dog("Jack", 3)
dog_2 = Dog("Thatcher", 5)
# Call the bark method
dog_1.bark() # JACK says woof woof! I'm 3 years old!
dog_2.bark() # THATCHER says woof woof! I'm 5 years old!JACK says woof woof! I'm 3 years old!
<bound method Dog.bark of <__main__.Dog object at 0x79befbfeb170>>
What Are Methods and Attributes, and How Do They Work?¶
Attributes → ables (holds data) that belong to an object
Two types :
Instance Attributes
unique to each obj created from a class
useally set with
_ _init_ _method
Class Attributes
belong to the class itself
shared by all instances of that class
To access attributes → use dot notation
# examples of both instance and class attributes,
# and how to access them from objects:
class Dog:
species = "French Bulldog" # Class attribute
def __init__(self, name):
self.name = name # Instance attribute `name` created
print(Dog.species) # French Bulldog
dog1 = Dog("Jack")
print(dog1.name) # Jack
print(dog1.species) # French Bulldog
dog2 = Dog("Tom")
print(dog2.name) # Tom <- using dot notation to access attribute
print(dog2.species) # French Bulldog <-|French Bulldog
Jack
French Bulldog
Tom
French Bulldog
class attributes can be access from → the class itslef
to access instance attribute → first create an obj & pass its data
class Car:
def __init__ (self, color, model):
self.color = color
self.model = model
car1 = Car("red", "Lambo")
car2 = Car("yellow", "Farari")
print(f'I have {car1.color} {car1.model}' )
print(f'I have {car2.color} {car2.model}' )I have red Lambo
I have yellow Farari
Methods → Func. defined inside a class
access methods → use dot notation
class Dog:
species = "French Bulldog"
def __init__(self, name): # __init__() -> a method
self.name = name
def bark(self): # bark() -> a method
return f"{self.name} says woof woof!"
jack = Dog("Jack")
jill = Dog("Jill")
print(jack.bark()) # Jack says woof woof!
print(jill.bark()) # Jill says woof woof!Jack says woof woof!
Jill says woof woof!
What Are Special Methods and What Are They Used For?¶
special methods / magic methods / dunder methods¶
start & end with double underscores (
_ _)dunder → d - double , under - underscores
link between programmer & py Interpreter
we don’t call them - py call them automatically when certain action happen
arithmaic operations:
__add__()→ Addition__sub__()→ Subtraction__mul__()→ Multiplication__truediv__()→ Division
String operations:
concatenation →
__add__()repetition →
__mul__()formatting →
__format__()conversion to text →
__str__()&__repr__()
Comparison Operations:
equality →
__eq__()less-than →
__lt__()greater-than →
__gt__()
Iteration Operations:
return an iterable obj →
__iter__()fetchin the next item →
__next__()
example ⇒ 3 + 4 ≡ 3.__add__(4)
py data types like → str & num → know how to add string, do concatination, compare for equality, loops ⇒ when creating own class - py don’t know how to handle them automatically - then we use special methods
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
book1 = Book("Built Wealth Like a Boss", 420)
book2 = Book("Be Your Own Start", 420)
print(len(book1)) # TypeError: object of type 'Book' has no len()
# |-> py don't know how to get the length
print(str(book1)) # <__main__.Book object at 0x102ed2900>
# |-> default representation when we do not use __str__()
print(book1 == book2) # False even though they have the same number of pages
# |-> False. Py just checks if
# both objects are the same in memory, not by content.---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/tmp/ipykernel_618/195700227.py in <cell line: 0>()
7 book2 = Book("Be Your Own Start", 420)
8
----> 9 print(len(book1)) # TypeError: object of type 'Book' has no len()
10 # |-> py don't know how to get the length
11
TypeError: object of type 'Book' has no len()class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
def __len__(self):
return self.pages
def __str__(self):
return f"'{self.title}' has {self.pages} pages"
def __eq__(self, other):
return self.pages == other.pages
book1 = Book("Built Wealth Like a Boss", 420)
book2 = Book("Be Your Own Start", 420)
print(len(book1)) # 420
print(len(book2)) # 420
print(str(book1)) # 'Built Wealth Like a Boss' has 420 pages
print(str(book2)) # 'Be Your Own Start' has 420 pages
print(book1)
print(book2)
print(book1 == book2) # True420
420
'Built Wealth Like a Boss' has 420 pages
'Be Your Own Start' has 420 pages
'Built Wealth Like a Boss' has 420 pages
'Be Your Own Start' has 420 pages
True
Shoping Cart Example:¶
Add items to the cart
Remove items from the cart
Get the number of items in the cart
Check what items are in the cart
Check if a specific item is in the cart
Return or display an item at a specific index in the cart
__len__() to get the length of the items in the cart
__iter__() to loop through the items in the cart so you can see them
__contains__() to check if a specific item is in the cart
__getitem__() to return or display an item at a specific index in the cart
class Cart:
def __init__(self):
self.items = []
def add(self, item): # Py automatically translates into a special method
self.items.append(item)
def remove(self, item): # Py automatically translates into a special method
if item in self.items:
self.items.remove(item)
else:
print(f'{item} is not in cart')
def list_items(self):
return self.items
def __len__(self):
return len(self.items)
def __getitem__(self, index):
return self.items[index]
def __contains__(self, item):
return item in self.items
def __iter__(self):
return iter(self.items)cart = Cart()
cart.add('Laptop')
cart.add('Wireless mouse')
cart.add('Ergo keyboard')
cart.add('Monitor')
for item in cart:
print(item, end=' ') # Laptop Wireless mouse Ergo keyboard Monitor
print(len(cart)) # 4
print(cart[3]) # Monitor
print('Monitor' in cart) # True
print('banana' in cart) # False
cart.remove('Ergo keyboard')
print(cart.list_items()) # ['Laptop', 'Wireless mouse', 'Monitor']
cart.remove('banana') # banana is not in cartLaptop Wireless mouse Ergo keyboard Monitor 4
Monitor
True
False
['Laptop', 'Wireless mouse', 'Monitor']
banana is not in cart
How to Handle Object Attributes Dynamically?¶
access, modify, check, or even delete attributes using their names as variables, and not as fixed names in your code.
Py has built-in Func.¶
getattr() → read & attribute from an obj - when we don’t know its name until runtime | if the attribute do not exist ⇒ AttributeError if no default value provided
getattr(object, attribute_name, default_value)class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person('John Doe', 30)
print(getattr(person, 'name')) # John Doe
print(getattr(person, 'age')) # 30
print(getattr(person, 'city', 'Milano')) # Milano
# | |-> default value
# |->doesn't exist in `Person` classwhen the attribute name comes from a variable, such as from user input or some file.
In that case, you can’t use the regular object.attribute_name syntax because the attribute name is not fixed.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person('John Doe', 30)
attr_name = input('Enter the attribute you want to see: ')
# |-> name ----- John Doe
# |-> age ------ 30
# |-> anything else (ie. email) --- Attribute not found
# ^^ Benefit of Dinamic Attribute ^^
print(getattr(person, attr_name, 'Attribute not found'))Enter the attribute you want to see: name
John Doe
dir() func. → return a list off all the attribute names on the obj.
## introspection -> examining an obj. at runtime
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person('John Doe', 30)
# Loop through all attributes of the person object with dir() function
for attr in dir(person):
# Ignore dunder methods like __init__ or __str__ and regular methods
# \/ //
if not attr.startswith('__') and not callable(getattr(person, attr)):
value = getattr(person, attr)
print(f'{attr}: {value}')
# Output
# age: 30
# name: John Doeage: 30
name: John Doe
callable() → built-in func. returns
True if the obj. passed - a func. or method ← can be called
False if the obj. passed - attribute ← cann’t call
setattr() → create a new attribute / update an existing one dynamically
setattr(object, attribute_name, value)class Configuration:
pass # means: Create the class, but don't put anything inside it yet.
# Data loaded at runtime (like from a config or env file)
settings_data = {
'server_url': 'https://api.example.com',
'timeout_sec': 30,
'max_retries': 5
}
config_obj = Configuration()
# Dynamically set attributes using dictionary keys and values
for attr_name, attr_value in settings_data.items():
setattr(config_obj, attr_name, attr_value)
print(config_obj.server_url) # https://api.example.com
print(config_obj.timeout_sec) # 30
print(config_obj.max_retries) # 5https://api.example.com
30
5
hasattr() → checks if an attribute exist - return True / False
hasattr(object, attribute_name)class Product:
def __init__(self, name, price):
self.name = name
self.price = price
# inventory_id -> missing here
product_a = Product('T-Shirt', 25) #inventory_id -> missing here
required_attributes = ['name', 'price', 'inventory_id']
for attr in required_attributes:
if not hasattr(product_a, attr):
print(f"ERROR: Product is missing the required attribute: '{attr}'")
else:
# Access the attributes dynamically once its existence is confirmed
print(f'{attr}: {getattr(product_a, attr)}')
name: T-Shirt
price: 25
ERROR: Product is missing the required attribute: 'inventory_id'
delattr() → lets you remove an attribute dynamically
delattr(object, attribute_name)class UserSession:
def __init__(self, user_id, token):
self.user_id = user_id
self.auth_token = token # sensitive
self.temp_counter = 0 # temporary
session = UserSession(101, 'a1b2c3d4e5')
# List of attributes to remove dynamically before "saving" the session
attributes_to_clean = ['auth_token', 'temp_counter']
# Dynamically remove specified attributes
for attr in attributes_to_clean:
if hasattr(session, attr):
delattr(session, attr)
print(f'Removed attribute: {attr}')
print('\nFinal attributes remaining:')
# Loop through the remaining attributes with dir()
for attr in dir(session):
# Ignore dunder methods like __init__ or __str__ and regular methods
if not attr.startswith('__') and not callable(getattr(session, attr)):
print(f' - {attr}: {getattr(session, attr)}')Removed attribute: auth_token
Removed attribute: temp_counter
Final attributes remaining:
- user_id: 101
datetime formatting.¶
The datetime.datetime.now() function gives the current date and time, and you can use the strftime() method to format it in different ways.
Here’s how strftime() works with format codes:
Example Code
now = datetime.datetime.now()
print(now.strftime("%Y-%m-%d")) # Output: 2024-03-15 (year-month-day with - separator)The format codes like %Y (year), %m (month), %d (day) tell strftime() what to include, and you can add separators like - between them.
Before sending the emails, add the standard Python idiom if name == ‘main’: followed by a call to main(). This ensures that the main function only runs when the script is executed directly, not when it’s imported as a module.