PY

Python Fundamentals

18 lessons

Progress0%
1. Introduction to Python
1What is Python?2Setting Up Python
2. Variables and Data Types
1Variables in Python2Data Types
3. Control Flow
Conditional StatementsLoops
4. Functions
Function Basics
5. Data Structures
Lists Deep DiveTuples & SetsDictionaries & Comprehensions
6. Advanced Functions
Closures & Higher-Order FunctionsDecorators & Lambda
7. Object-Oriented Python
Classes & InstancesInheritance & Dunder Methods
8. Exception Handling
try / except / finallyCustom Exceptions & Context Managers
9. Modules & File I/O
Modules & PackagesFile I/O & JSON
All Tutorials
PythonObject-Oriented Python
Lesson 13 of 18 min
Chapter 7 · Lesson 1

Classes & Instances

Classes & Instances

Object-Oriented Programming (OOP) models the world as objects that combine data (attributes) and behaviour (methods). Python supports OOP fully while also allowing other styles.

Defining a Class

python
class Dog:
    species = "Canis familiaris"    # class variable — shared by all instances

    def __init__(self, name, age):  # initialiser
        self.name = name            # instance variable
        self.age  = age

    def bark(self):
        return f"{self.name} says Woof!"
  • __init__ is called automatically when you create an instance.
  • self refers to the specific instance being created or operated on.
  • Class variables are shared; instance variables belong to one object.

str and repr

python
def __str__(self):   # human-readable; called by print() and str()
    return f"Dog(name={self.name}, age={self.age})"

def __repr__(self):  # unambiguous; called in the REPL and by repr()
    return f"Dog({self.name!r}, {self.age!r})"

@property

@property turns a method into a read-only attribute. Pair it with @<name>.setter for validation:

python
class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value

    @property
    def area(self):
        import math
        return math.pi * self._radius ** 2

@classmethod and @staticmethod

DecoratorFirst paramWhen to use
(none)self — the instanceRegular methods
@classmethodcls — the class itselfAlternative constructors, class-level operations
@staticmethod(none)Utility functions logically grouped with the class
python
class Date:
    def __init__(self, y, m, d):
        self.year, self.month, self.day = y, m, d

    @classmethod
    def from_string(cls, s):          # factory: Date.from_string("2024-01-15")
        y, m, d = map(int, s.split("-"))
        return cls(y, m, d)

    @staticmethod
    def is_leap(year):                # utility: Date.is_leap(2024)
        return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

Code Examples

Class Basics: __init__, __str__, __repr__python
class BankAccount:
    bank_name = "PyBank"  # class variable

    def __init__(self, owner, balance=0.0):
        self.owner   = owner
        self.balance = balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("Deposit must be positive")
        self.balance += amount
        return self.balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("Insufficient funds")
        self.balance -= amount
        return self.balance

    def __str__(self):
        return f"{self.bank_name} account | Owner: {self.owner} | Balance: ${self.balance:.2f}"

    def __repr__(self):
        return f"BankAccount(owner={self.owner!r}, balance={self.balance!r})"

acc = BankAccount("Alice", 1000.0)
acc.deposit(500)
acc.withdraw(200)
print(acc)
print(repr(acc))
print("Bank:", BankAccount.bank_name)

__str__ is meant for end users and is called by print(). __repr__ is for developers — it should ideally be a string you could eval() to recreate the object. Class variables like bank_name are shared across all instances.

@property with Validationpython
import math

class Circle:
    def __init__(self, radius):
        self.radius = radius  # calls the setter

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError(f"Radius must be non-negative, got {value}")
        self._radius = value

    @property
    def diameter(self):
        return self._radius * 2

    @property
    def area(self):
        return round(math.pi * self._radius ** 2, 4)

    @property
    def circumference(self):
        return round(2 * math.pi * self._radius, 4)

c = Circle(5)
print(f"Radius: {c.radius}")
print(f"Diameter: {c.diameter}")
print(f"Area: {c.area}")
print(f"Circumference: {c.circumference}")

c.radius = 10
print(f"New area: {c.area}")

@property converts a method into a computed attribute. The setter validates the input before storing it in the private _radius attribute. Derived properties like area and circumference are always computed fresh from the current radius.

@classmethod & @staticmethodpython
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    @classmethod
    def from_fahrenheit(cls, f):
        return cls((f - 32) * 5 / 9)

    @classmethod
    def from_kelvin(cls, k):
        return cls(k - 273.15)

    @staticmethod
    def is_freezing(celsius):
        return celsius <= 0

    @property
    def fahrenheit(self):
        return self.celsius * 9 / 5 + 32

    def __str__(self):
        return f"{self.celsius:.2f} C / {self.fahrenheit:.2f} F"

t1 = Temperature(100)
t2 = Temperature.from_fahrenheit(212)
t3 = Temperature.from_kelvin(373.15)
print(t1)
print(t2)
print(t3)
print("Is -5C freezing?", Temperature.is_freezing(-5))
print("Is 20C freezing?", Temperature.is_freezing(20))

@classmethod receives cls as the first argument, letting it create instances via cls(...) — perfect for alternative constructors. @staticmethod receives no implicit first argument; it's a plain function that logically belongs to the class namespace.

Quick Quiz

1. What is the difference between a class variable and an instance variable?

2. When is __str__ called automatically?

3. What is the primary use case for @classmethod?

Was this lesson helpful?

PreviousNext