Latest update Android YouTube

Object-Oriented Programming | OOP in Python

Object-Oriented Programming in Python

Mastering classes, objects, and Python's OOP features

1. Classes and Objects Fundamentals

Core Concepts

What is a Class?

A class is a blueprint for creating objects. It defines attributes (data) and methods (functions) that the created objects will have.

What is an Object?

An object is an instance of a class. It's a concrete "thing" created from the class blueprint, with its own set of attributes.

# Class definition
class Dog:
    """A simple Dog class"""
    
    def bark(self):
        print("Woof!")

# Object creation
my_dog = Dog()
my_dog.bark()  # Output: Woof!

The self Parameter

self refers to the instance calling the method. Python automatically passes it when calling instance methods.

Note: While self is the convention, you can technically use any name, but don't!

Object Identity and Comparison

id() Function

Returns the memory address of an object:

a = [1, 2, 3]
print(id(a))  # Memory address like 140245123456

is vs ==

Operator Description Example
is Identity comparison (same object in memory) a is b
== Equality comparison (same value) a == b
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = list1

print(list1 == list2)  # True (same content)
print(list1 is list2)  # False (different objects)
print(list1 is list3)  # True (same object)

Object Lifecycle

Python uses reference counting and garbage collection to manage memory:

  • Objects are created when instantiated
  • Memory is reclaimed when no references remain
  • The __del__ method is called before destruction

2. Class Definition and Structure

Basic Class Definition

class Person:
    """A class representing a person.
    
    Attributes:
        name (str): The person's name
        age (int): The person's age
    """
    
    species = "Homo sapiens"  # Class attribute
    
    def __init__(self, name, age):
        self.name = name  # Instance attribute
        self.age = age    # Instance attribute

Naming Conventions

  • Class names: PascalCase
  • Method names: snake_case
  • Private conventions: _single_leading_underscore (protected), __double_leading_underscore (name mangled)

Empty Classes

Use pass as a placeholder:

class MyEmptyClass:
    pass

Advanced Class Features

Type Hints in Classes

class Point:
    def __init__(self, x: float, y: float) -> None:
        self.x = x
        self.y = y
    
    def move(self, dx: float, dy: float) -> 'Point':
        self.x += dx
        self.y += dy
        return self

Abstract Base Classes (ABC)

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self) -> float:
        return 3.14 * self.radius ** 2

Class Decorators

from dataclasses import dataclass
from typing import final

@dataclass
class Point:
    x: float
    y: float

@final
class CannotBeInherited:
    pass

3. Attributes and Methods

Instance Attributes

Adding Attributes Dynamically

class Dog:
    pass

d = Dog()
d.name = "Fido"  # Added dynamically
d.age = 3

__dict__ Attribute

Contains the instance's namespace dictionary:

print(d.__dict__)  # {'name': 'Fido', 'age': 3}

Attribute Access Order

Python searches for attributes in this order:

  1. Instance attributes
  2. Class attributes
  3. Parent class attributes (inheritance)

Method Types

Method Type Description Example
Instance Method Receives instance as first arg (self) def method(self, args):
Class Method (@classmethod) Receives class as first arg (cls) @classmethod
def method(cls, args):
Static Method (@staticmethod) Receives no special first arg @staticmethod
def method(args):
Property Method (@property) Defines getter for computed attribute @property
def value(self):
class MyClass:
    @classmethod
    def class_method(cls):
        print(f"Called class_method of {cls}")
    
    @staticmethod
    def static_method():
        print("Called static_method")
    
    @property
    def computed_prop(self):
        return "Computed value"

4. Constructors and Initialization

__init__ Method

The constructor method called when an instance is created:

class Person:
    def __init__(self, name, age=18):  # Default age
        self.name = name
        self.age = age

p = Person("Alice", 25)

Calling Parent Constructors

class Employee(Person):
    def __init__(self, name, age, employee_id):
        super().__init__(name, age)  # Call parent __init__
        self.employee_id = employee_id

Other Special Methods

__new__ Method

Controls instance creation (rarely used directly):

class Singleton:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

Object Representation Methods

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __str__(self):
        return f"Point at ({self.x}, {self.y})"
    
    def __repr__(self):
        return f"Point(x={self.x}, y={self.y})"

p = Point(3, 4)
print(str(p))   # Point at (3, 4)
print(repr(p))  # Point(x=3, y=4)

5. Instance vs Class Variables

Instance Variables

Unique to each instance, defined in __init__:

class Dog:
    def __init__(self, name):
        self.name = name  # Instance variable

d1 = Dog("Fido")
d2 = Dog("Buddy")
print(d1.name, d2.name)  # Fido Buddy

Class Variables

Shared among all instances, defined in class body:

class Dog:
    species = "Canis familiaris"  # Class variable
    
    def __init__(self, name):
        self.name = name

d1 = Dog("Fido")
d2 = Dog("Buddy")
print(d1.species, d2.species)  # Same for both

Dog.species = "Canis lupus"  # Changes all instances
print(d1.species)  # Canis lupus

Warning: Be careful with mutable class variables as they're shared across all instances!

Common Use Cases

  • Constants (e.g., PI = 3.14159)
  • Counters (e.g., instance counting)
  • Default values

6. Advanced OOP Concepts

Name Mangling (__var)

Python adds class name prefix to "private" attributes:

class MyClass:
    def __init__(self):
        self.__private = "secret"  # Name mangled to _MyClass__private

obj = MyClass()
print(obj.__dict__)  # {'_MyClass__private': 'secret'}

Property Decorators

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        """Get the radius"""
        return self._radius
    
    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError("Radius must be positive")
        self._radius = value
    
    @radius.deleter
    def radius(self):
        print("Deleting radius")
        del self._radius

c = Circle(5)
print(c.radius)  # Uses getter
c.radius = 10    # Uses setter
del c.radius     # Uses deleter

Class Composition

"Has-a" relationship (alternative to inheritance):

class Engine:
    def start(self):
        print("Engine started")

class Car:
    def __init__(self):
        self.engine = Engine()  # Composition
    
    def start(self):
        self.engine.start()

my_car = Car()
my_car.start()  # Engine started

7. Practical Patterns and Best Practices

Common Patterns

Singleton Pattern

class Singleton:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

a = Singleton()
b = Singleton()
print(a is b)  # True

Factory Method

class ShapeFactory:
    @staticmethod
    def create_shape(shape_type):
        if shape_type == "circle":
            return Circle()
        elif shape_type == "square":
            return Square()
        raise ValueError("Invalid shape type")

shape = ShapeFactory.create_shape("circle")

Performance Considerations

__slots__ Optimization

Reduces memory usage by preventing dynamic attribute creation:

class Point:
    __slots__ = ['x', 'y']  # Only these attributes allowed
    
    def __init__(self, x, y):
        self.x = x
        self.y = y

Method Resolution Order (MRO)

Determines method lookup order in inheritance:

class A:
    def method(self):
        print("A")

class B(A):
    def method(self):
        print("B")
        super().method()

class C(A):
    def method(self):
        print("C")
        super().method()

class D(B, C):
    def method(self):
        print("D")
        super().method()

d = D()
d.method()
# Output:
# D
# B
# C
# A

print(D.__mro__)  # Shows method resolution order

8. Debugging and Introspection

Inspection Tools

type() and isinstance()

class Animal: pass
class Dog(Animal): pass

d = Dog()
print(type(d))          # <class '__main__.Dog'>
print(isinstance(d, Dog))    # True
print(isinstance(d, Animal)) # True (inheritance)

dir() Function

Lists attributes and methods of an object:

print(dir(d))  # ['__class__', '__delattr__', ...]

inspect Module

import inspect

print(inspect.getmembers(d))  # Detailed member info
print(inspect.getsource(Dog)) # Shows class source code

Common Pitfalls

Mutable Default Arguments

class BadIdea:
    def __init__(self, items=[]):  # Shared across instances!
        self.items = items

a = BadIdea()
a.items.append(1)
b = BadIdea()
print(b.items)  # [1] - Surprise!

Accidental Class Variable Sharing

class Dog:
    tricks = []  # Shared by all dogs!
    
    def add_trick(self, trick):
        self.tricks.append(trick)

d1 = Dog()
d2 = Dog()
d1.add_trick("roll over")
print(d2.tricks)  # ['roll over'] - Oops!

Complete Code Example

class Person:
    """A class representing a person"""
    species = "Homo sapiens"  # Class variable
    
    def __init__(self, name: str, age: int):
        self.name = name  # Instance variable
        self.age = age    # Instance variable
    
    @classmethod
    def from_birth_year(cls, name: str, birth_year: int) -> 'Person':
        """Alternative constructor"""
        return cls(name, 2023 - birth_year)
    
    @property
    def is_adult(self) -> bool:
        """Computed property"""
        return self.age >= 18
    
    def __str__(self) -> str:
        return f"{self.name} ({self.age})"
    
    def __repr__(self) -> str:
        return f"Person(name='{self.name}', age={self.age})"

# Usage
p1 = Person("Alice", 25)
p2 = Person.from_birth_year("Bob", 1990)

print(p1)           # Alice (25)
print(repr(p2))     # Person(name='Bob', age=33)
print(p1.is_adult)  # True

Post a Comment

Feel free to ask your query...
Cookie Consent
We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.
Oops!
It seems there is something wrong with your internet connection. Please connect to the internet and start browsing again.
AdBlock Detected!
We have detected that you are using adblocking plugin in your browser.
The revenue we earn by the advertisements is used to manage this website, we request you to whitelist our website in your adblocking plugin.
Site is Blocked
Sorry! This site is not available in your country.