Object Oriented Programming (OOP) - CameronAuler/python-devops GitHub Wiki
Table of Contents
- Object-Oriented Programming (OOP)
- Classes & Objects
- Instance & Class Variables
- Methods: Instance, Class, & Static
- Inheritance
- Polymorphism
- Encapsulation
- Magic Methods & Operator Overloading
Object-Oriented Programming (OOP) is a programming paradigm that organizes code using objects and classes. It promotes code reusability, modularity, and organization by modeling real-world entities as objects.
Classes & Objects
Class Definition
A class is a blueprint for creating objects. It defines attributes (variables) and methods (functions) that describe the behavior of objects. An object is an instance of a class with its own unique data.
class Car:
def __init__(self, brand, model):
self.brand = brand # Instance variable
self.model = model # Instance variable
# Creating objects (instances)
car1 = Car("Toyota", "Corolla")
car2 = Car("Honda", "Civic")
print(car1.brand, car1.model) # Accessing instance attributes
print(car2.brand, car2.model)
# Output:
Toyota Corolla
Honda Civic
Instance & Class Variables
Instance Variables
Instance variables are unique to each instance (object) and are defined inside __init__
using self
.
class Dog:
def __init__(self, name):
self.name = name # Instance variable
dog1 = Dog("Buddy")
dog2 = Dog("Charlie")
print(dog1.name) # Output: Buddy
print(dog2.name) # Output: Charlie
Class Variables
Class variables are Shared across all instances and defined outside __init__
using the class name.
class Animal:
species = "Mammal" # Class variable
def __init__(self, name):
self.name = name # Instance variable
animal1 = Animal("Lion")
animal2 = Animal("Tiger")
print(animal1.species) # Output: Mammal
print(animal2.species) # Output: Mammal
Animal.species = "Reptile"
print(animal1.species) # Output: Reptile
Methods: Instance, Class, & Static
Instance Methods
Instance methods operate on instance variables and require self
as the first parameter.
class Person:
def __init__(self, name):
self.name = name
def greet(self): # Instance method
return f"Hello, my name is {self.name}."
person = Person("Alice")
print(person.greet())
# Output:
Hello, my name is Alice.
Class Methods
Class methods operate on class variables and are defined using @classmethod
and take cls
as the first parameter.
class Employee:
company = "TechCorp"
def __init__(self, name):
self.name = name
@classmethod
def change_company(cls, new_company):
cls.company = new_company
Employee.change_company("NewTech")
print(Employee.company) # Output: NewTech
Static Methods
Static methods are independent of class or instance variables and are defined using @staticmethod
.
class MathUtils:
@staticmethod
def add(x, y):
return x + y
print(MathUtils.add(3, 5)) # Output: 8
Inheritance
Inheritance allows a class to derive properties and methods from another class.
Single Inheritance
A child class inherits attributes and methods from a parent class.
class Vehicle:
def __init__(self, brand):
self.brand = brand
def show_brand(self):
return f"Brand: {self.brand}"
class Car(Vehicle): # Inheriting from Vehicle
def __init__(self, brand, model):
super().__init__(brand) # Call parent constructor
self.model = model
def show_model(self):
return f"Model: {self.model}"
car = Car("Toyota", "Camry")
print(car.show_brand())
print(car.show_model())
# Output:
Brand: Toyota
Model: Camry
Multiple Inheritance
A class can inherit from multiple parent classes.
class Engine:
def engine_type(self):
return "V8 Engine"
class Wheels:
def wheel_type(self):
return "Alloy Wheels"
class Car(Engine, Wheels): # Multiple Inheritance
pass
my_car = Car()
print(my_car.engine_type())
print(my_car.wheel_type())
# Output:
V8 Engine
Alloy Wheels
Polymorphism
Polymorphism allows different classes to use the same method name but implement different behaviors.
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
def animal_sound(animal):
return animal.speak()
dog = Dog()
cat = Cat()
print(animal_sound(dog))
print(animal_sound(cat))
# Output:
Woof!
Meow!
Encapsulation
Encapsulation restricts direct access to certain data to prevent unauthorized access.
Private Attributes
Prefix variables with double underscore (__
) to make them private.
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Private variable
def deposit(self, amount):
self.__balance += amount
def get_balance(self):
return self.__balance # Only accessible via method
account = BankAccount(1000)
account.deposit(500)
print(account.get_balance()) # Output: 1500
# print(account.__balance) # This would raise an AttributeError
Magic Methods & Operator Overloading
Magic methods (dunder methods) define special behaviors for objects. __str__
returns a human-readable string representation while __repr__
is used for debugging.
__str__
& __repr__
Methods
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def __str__(self): # Readable representation
return f"{self.title} by {self.author}"
def __repr__(self): # Debugging representation
return f"Book('{self.title}', '{self.author}')"
book = Book("1984", "George Orwell")
print(book) # Calls __str__
print(repr(book)) # Calls __repr__
# Output:
1984 by George Orwell
Book('1984', 'George Orwell')
Operator Overloading
Operator overloading allows custom objects to define how built-in operators (+
, -
, *
, /
, ==
, etc.) behave when used with them. This makes objects more intuitive to work with by enabling natural syntax instead of calling explicit methods. Normally, Python does not allow arithmetic operations on custom objects unless explicitly defined. Operator overloading enables this functionality by defining special dunder methods (double underscore methods like __add__
, __sub__
, etc.).
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other): # Overloading the `+` operator
return Point(self.x + other.x, self.y + other.y)
def __sub__(self, other): # Overloading the `-` operator
return Point(self.x - other.x, self.y - other.y)
def __str__(self):
return f"({self.x}, {self.y})"
p1 = Point(2, 3)
p2 = Point(4, 5)
p3 = p1 + p2 # Calls __add__
p4 = p1 - p2 # Calls __sub__
print(p3) # (6, 8)
print(p4) # (-2, -2)
# Output:
(6, 8)
(-2, -2)
Overload Operators
Operator | Method | Description |
---|---|---|
+ |
__add__(self, other) |
Defines behavior for self + other . |
- |
__sub__(self, other) |
Defines behavior for self - other . |
* |
__mul__(self, other) |
Defines behavior for self * other . |
/ |
__truediv__(self, other) |
Defines behavior for self / other . |
// |
__floordiv__(self, other) |
Defines behavior for self // other . |
% |
__mod__(self, other) |
Defines behavior for self % other . |
** |
__pow__(self, other) |
Defines behavior for self ** other . |
<< |
__lshift__(self, other) |
Defines behavior for self << other (bitwise left shift). |
>> |
__rshift__(self, other) |
Defines behavior for self >> other (bitwise right shift). |
& |
__and__(self, other) |
Defines behavior for self & other (bitwise AND). |
| |
__or__(self, other) |
Defines behavior for `self |
^ |
__xor__(self, other) |
Defines behavior for self ^ other (bitwise XOR). |
~ |
__invert__(self) |
Defines behavior for ~self (bitwise NOT). |
== |
__eq__(self, other) |
Defines behavior for self == other . |
!= |
__ne__(self, other) |
Defines behavior for self != other . |
< |
__lt__(self, other) |
Defines behavior for self < other . |
> |
__gt__(self, other) |
Defines behavior for self > other . |
<= |
__le__(self, other) |
Defines behavior for self <= other . |
>= |
__ge__(self, other) |
Defines behavior for self >= other . |
() |
__call__(self, *args, **kwargs) |
Defines behavior for calling an instance like a function. |
[] |
__getitem__(self, key) |
Defines behavior for self[key] . |
[]= |
__setitem__(self, key, value) |
Defines behavior for self[key] = value . |
del [] |
__delitem__(self, key) |
Defines behavior for del self[key] . |
len() |
__len__(self) |
Defines behavior for len(self) . |
repr() |
__repr__(self) |
Defines unambiguous string representation of the object. |
str() |
__str__(self) |
Defines human-readable string representation. |
hash() |
__hash__(self) |
Defines behavior for hash(self) , needed for dict keys & sets. |
bool() |
__bool__(self) |
Defines truthy or falsy behavior in conditionals. |
iter() |
__iter__(self) |
Defines iterator behavior for iter(self) . |
next() |
__next__(self) |
Defines behavior for next(self) , used in loops. |
contains() |
__contains__(self, item) |
Defines behavior for item in self . |
enter / exit |
__enter__(self) , __exit__(self, exc_type, exc_value, traceback) |
Defines behavior for context managers (with statement). |