Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

How Do Classes Work and How Do They Differ From Objects?

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 do

Attributes : 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 self we 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 + 43.__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) # True
420
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 cart
Laptop 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` class

when 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 Doe
age: 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) # 5
https://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.