Foundations of Object-Oriented Programming in Python
Defining Classes and Instances
A class acts as a blueprint for creating specific objects. Variables defined directly within the class body are referred to as member variables.
class UserProfile:
username = None
role = None
active_status = None
Instantiating the class generates an independent object:
user_instance = UserProfile()
Attributes can be modified individually after creation:
user_instance.username = "admin"
user_instance.role = "manager"
Functions defined inside a class are known as member methods. The first parameter self is mandatory when defining them, representing the instance itself.
def display_info(self, param_one, param_two):
pass
When calling these methods on an instance, passing the object as an argument for self is typically handled automatically by the interpreter.
Initializing Objects
Special constructors allow customization immediately upon object generation. The __init__ method executes automatically during instantiation, receiving parameters passed from the constructor call.
class ConfigHandler:
def __init__(self, base_path, config_file):
self.base_path = base_path
self.config_file = config_file
The keyword self must also appear here. Accessing member variables inside methods requires referencing self.
Special Methods
Python supports special methods (often called magic methods) to define operator overloading and object behavior.
String Representation
The __str__ method controls how an object is displayed when converted to a string format.
Comparison Operators
Methods like __lt__ (less than), __le__ (less than or equal), and __eq__ (equal) customize comparison logic between instances.
Callability
Defining __call__ makes an instance behave like a function. When called, it executes the code within that method.
class ScriptRunner:
pass
runner = ScriptRunner()
print(f"is callable: {callable(runner)}") # Output: False
By adding __call__:
class ScriptRunner:
def __call__(self):
print("Executing task script")
runner = ScriptRunner()
print(f"is callable: {callable(runner)}") # Output: True
runner()
Encapsulation Strategies
Encapsulation restricts access to certain components to maintain data integrity.
- Protected Members: Single underscore prefix (e.g.,
_var) suggests internal use but remains accessible. - Private Members: Double underscore prefix (e.g.,
__var) triggers name mangling. Python renames these to_ClassName__var, preventing direct external access even from subclasses.
Inheritance Mechanisms
Subclasses derive from parent classes to reuse and extend functionality.
class NetworkDevice:
device_id = None
manufacturer = "Generic"
def transmit_data(self):
print("Sending via standard protocol")
class Router(NetworkDevice):
manufacturer = "NetBrand"
def transmit_data(self):
# Direct parent access
print(f"Using parent brand: {NetworkDevice.manufacturer}")
NetworkDevice.transmit_data(self)
# Using super()
print(f"Using parent brand: {super().manufacturer}")
super().transmit_data()
Multiple inheritance is supported. If parents share attributes, priority follows the order of declaration (leftmost takes precedence). Overriding occurs when a subclass defines a method already present in the parent.
To invoke overridden parent members:
- Direct Access: Use
ParentClass.method_name(self). - Super Function: Use
super().method_name().
Type Hints
Static type checking can be enhanced using annotations to assist IDEs with inference and documentation.
count: int = 10
flags: bool = True
ids: list[int] = [1, 2, 3]
pairs: dict[str, int] = {"key": 1}
Complex container types require specifying element types:
my_tuple: tuple[str, int, bool] = ("abc", 123, True)
Function annotations include parameter types and return values:
def process_data(item: list[int]) -> str:
return f"Processed {len(item)} items"
For flexible typing, Union types allow multiple possibilities:
from typing import Union
value: list[Union[str, int]] = [1, "hello"]
def handle_input(data: Union[int, str]) -> Union[int, str]:
pass
Polymorphism
Polymorphism allows interface-based design where specific implementations vary while sharing a common signature. Base classes establish abstract standards without implementation logic.
class SmartAppliance:
def activate_mode(self):
pass
class Thermostat(SmartAppliance):
def activate_mode(self):
print("Heating active")
class LightSwitch(SmartAppliance):
def activate_mode(self):
print("Light turned on")
Abstract parent classes define contracts, while concrete subclasses provide specific logic, enabling flexible system design.