Python Classes/Objects
Python Object-Oriented Programming (OOP)
Section titled “Python Object-Oriented Programming (OOP)”Python is fundamentally an object-oriented language. Creating and using classes and objects is a core part of programming effectively in Python. This section introduces the key concepts of Python’s OOP support.
If you’re new to Object-Oriented Programming (OOP), understanding concepts like classes, objects, inheritance, encapsulation, and polymorphism is crucial. Consider reviewing introductory OOP materials.
Core OOP Terminology
Section titled “Core OOP Terminology”- Class: A blueprint or template for creating objects. Defines attributes (data) and methods (functions) that the objects created from the class will have.
- Object (Instance): A specific realization of a class. If
Dogis a class, then a specific dog likemy_dog = Dog()is an object or instance of theDogclass. - Attribute: A variable associated with a class or an object.
* Class Variable: Shared by all instances of a class. Defined within the class but outside any methods.
* Instance Variable: Belongs to a specific instance/object. Typically defined within the
__init__method usingself.variable_name. - Method: A function defined within a class. Operates on objects (instances) of that class. The first parameter of an instance method is conventionally named
self, representing the instance itself. __init__Method (Initializer/Constructor): A special method called automatically when a new object (instance) of a class is created. Used to initialize the object’s attributes.self: A conventional name for the first parameter of instance methods, representing the instance on which the method is called.- Inheritance: A mechanism where a new class (child/subclass) derives attributes and methods from an existing class (parent/superclass). Promotes code reuse.
- Encapsulation: Bundling data (attributes) and methods that operate on the data within a single unit (class). Often involves controlling access to internal details (e.g., using private attributes).
- Polymorphism: The ability of objects of different classes to respond to the same method call in a way specific to their class type (‘many forms’).
Creating Classes
Section titled “Creating Classes”Use the class keyword followed by the class name (typically CamelCase) and a colon. The indented block contains the class definition.
class ClassName: """Optional docstring describing the class.""" # Class body: attributes and methods pass # Use 'pass' for an empty class bodyExample: A Simple Dog Class
Section titled “Example: A Simple Dog Class”class Dog: """A simple representation of a dog."""
# Class variable (shared by all Dog instances) species = "Canis familiaris"
def __init__(self, name, age): """Initialize the dog's name and age.""" # Instance variables (unique to each instance) self.name = name self.age = age print(f"Dog named {self.name} created!")
# Instance method def bark(self): """Simulate the dog barking.""" print(f"{self.name} says: Woof!")
# Another instance method def describe(self): """Describe the dog.""" print(f"{self.name} is {self.age} years old.")Key points:
Creating Instance Objects
Section titled “Creating Instance Objects”To create an object (instance) of a class, call the class name as if it were a function, passing any arguments required by the __init__ method (excluding self).
# Create two Dog objectsmy_dog = Dog("Buddy", 3)your_dog = Dog("Lucy", 5)Output from __init__:
Dog named Buddy created!Dog named Lucy created!Accessing Attributes and Calling Methods
Section titled “Accessing Attributes and Calling Methods”Use the dot operator (.) to access an object’s attributes (instance variables) and call its methods.
# Access instance variablesprint(f"{my_dog.name} is {my_dog.age} years old.")
# Call instance methodsmy_dog.bark()your_dog.describe()
# Access class variable (can be accessed via instance or class)print(f"Buddy belongs to the species: {my_dog.species}")print(f"All dogs belong to: {Dog.species}")Output:
Buddy is 3 years old.Buddy says: Woof!Lucy is 5 years old.Buddy belongs to the species: Canis familiarisAll dogs belong to: Canis familiarisModifying Attributes
Section titled “Modifying Attributes”You can modify instance attributes directly (if they are not intended to be private). You can also add new attributes to specific instances dynamically.
my_dog.age = 4 # Modify existing attributemy_dog.breed = "Golden Retriever" # Add a new attribute to my_dog only
print(f"Buddy's updated age: {my_dog.age}")print(f"Buddy's breed: {my_dog.breed}")
# your_dog does not have the 'breed' attribute initially# print(your_dog.breed) # Would raise AttributeError
# Modifying class variables affects all instancesDog.species = "A good doggo"print(f"Lucy's species now: {your_dog.species}")Built-in Attribute Functions:
getattr(obj, name[, default]): Accesses the attributenameofobj. Returnsdefaultif specified and attribute doesn’t exist, otherwise raisesAttributeError.hasattr(obj, name): ReturnsTrueifobjhas an attributename,Falseotherwise.setattr(obj, name, value): Sets the attributenameonobjtovalue. Creates the attribute if it doesn’t exist.delattr(obj, name): Deletes the attributenamefromobj.
Built-In Class Attributes
Section titled “Built-In Class Attributes”Python classes have several built-in attributes, accessible via dot notation:
ClassName.__doc__: The class’s documentation string (orNone).ClassName.__name__: The class name.ClassName.__module__: The name of the module where the class was defined.ClassName.__bases__: A tuple containing the parent classes (base classes).ClassName.__dict__: A dictionary containing the class’s namespace (class variables, methods, etc.).instance.__class__: The class to which an instance belongs.
print(f"Dog.__doc__: {Dog.__doc__}")print(f"Dog.__name__: {Dog.__name__}")print(f"Dog.__module__: {Dog.__module__}")print(f"Dog.__bases__: {Dog.__bases__}") # Empty tuple if no explicit inheritance# print(f"Dog.__dict__: {Dog.__dict__}") # Shows class attributes/methodsprint(f"my_dog.__class__: {my_dog.__class__}")Garbage Collection and Destructors (__del__)
Section titled “Garbage Collection and Destructors (__del__)”Python automatically manages memory using garbage collection, primarily based on reference counting. When an object’s reference count drops to zero (no variables point to it), Python reclaims its memory.
You can define a special method __del__(self), called a destructor, which Python attempts to call just before an object is garbage collected. It’s used for cleanup tasks like closing files or network connections associated with the object.
Caution: Relying on __del__ can be tricky due to the timing of garbage collection and potential issues with circular references. It’s often better to use context managers (with statement) for resource management.
class FileHandler: def __init__(self, filename): print(f"Opening {filename}") self.filename = filename # self.file = open(filename, 'w') # Example resource
def __del__(self): # Cleanup actions print(f"Destructor called for {self.filename}. Cleaning up.") # if hasattr(self, 'file') and not self.file.closed: # self.file.close()
fh = FileHandler("my_data.txt")# Sometime later, when 'fh' is no longer referenced...del fh # Manually trigger reference count drop# ... garbage collection eventually calls __del__Class Inheritance
Section titled “Class Inheritance”Inheritance allows a class (child/subclass) to inherit attributes and methods from another class (parent/superclass). Syntax: class ChildClass(ParentClass):
Example:
Section titled “Example:”class Animal: def __init__(self, name): self.name = name print("Animal created")
def speak(self): print("Generic animal sound")
def eat(self): print(f"{self.name} is eating.")
# Cat inherits from Animalclass Cat(Animal): def __init__(self, name, breed): print("Cat constructor called") # Call the parent class's __init__ method super().__init__(name) self.breed = breed
# Override the speak method def speak(self): print(f"{self.name} says: Meow!")
# Add a new method specific to Cat def purr(self): print(f"{self.name} is purring.")
# Create an instance of the child classmy_cat = Cat("Whiskers", "Siamese")
my_cat.eat() # Inherited method from Animalmy_cat.speak() # Overridden method in Catmy_cat.purr() # Method specific to Catprint(f"Breed: {my_cat.breed}")Output:
Cat constructor calledAnimal createdWhiskers is eating.Whiskers says: Meow!Whiskers is purring.Breed: Siamesesuper().__init__(...) is used to call the parent class’s initializer.
Multiple Inheritance: A class can inherit from multiple parents: class Child(Parent1, Parent2):. Python uses Method Resolution Order (MRO) to determine which parent method to call in case of conflicts (C3 linearization algorithm).
Overriding Methods
Section titled “Overriding Methods”A child class can provide its own implementation of a method inherited from a parent class. This is called overriding. See the speak method in the Cat example above.
Special (Magic/Dunder) Methods
Section titled “Special (Magic/Dunder) Methods”Python classes can define special methods with double underscores (__methodname__) that allow objects to interact with built-in Python operations (like addition, printing, comparison).
| Method | Operator/Function | Description |
|---|---|---|
__init__(self, ...) | Object creation | Initializer |
__str__(self) | str(), print() | Informal, human-readable string representation |
__repr__(self) | repr() | Official, unambiguous string representation (often code to recreate object) |
__len__(self) | len() | Length of the object |
__add__(self, other) | + | Addition |
__eq__(self, other) | == | Equality comparison |
__lt__(self, other) | < | Less than comparison |
__getitem__(self, key) | obj[key] | Access item by key (indexing/lookup) |
__call__(self, ...) | obj(...) | Make object callable like a function |
Example (Overloading + and __str__):
Section titled “Example (Overloading + and __str__):”class Vector: def __init__(self, x, y): self.x = x self.y = y
def __add__(self, other): """Defines vector addition using the + operator.""" if isinstance(other, Vector): return Vector(self.x + other.x, self.y + other.y) return NotImplemented # Indicate operation not supported
def __str__(self): """Defines how the vector is printed.""" return f"Vector({self.x}, {self.y})"
def __repr__(self): """Defines the 'official' representation.""" return f"Vector(x={self.x}, y={self.y})"
v1 = Vector(2, 3)v2 = Vector(5, -1)v3 = v1 + v2 # Uses the __add__ method
print(v1) # Uses __str__print(v3) # Uses __str__print(repr(v3)) # Uses __repr__Output:
Vector(2, 3)Vector(7, 2)Vector(x=7, y=2)Data Hiding (Name Mangling)
Section titled “Data Hiding (Name Mangling)”Python doesn’t have strict ‘private’ attributes like some languages. However, by prefixing an attribute name with two underscores (e.g., __secret), Python performs name mangling. It changes the attribute name internally to _ClassName__attributeName. This makes it harder (but not impossible) to access accidentally from outside the class, preventing naming conflicts in subclasses.
class MySecret: def __init__(self): self.public_var = 10 self.__secret_var = 20 # Name mangled
def get_secret(self): return self.__secret_var
secret_obj = MySecret()print(secret_obj.public_var) # Accessing public is fine
# print(secret_obj.__secret_var) # AttributeError!
# Access via mangled name (discouraged unless necessary)print(secret_obj._MySecret__secret_var)
print(secret_obj.get_secret()) # Access via method is preferredOutput:
102020Conventionally, a single underscore prefix (_variable) signals that an attribute or method is intended for internal use, but it’s not enforced by Python.
Properties and Data Classes
Section titled “Properties and Data Classes”Modern Python offers features like @property for creating managed attributes (getter/setter/deleter methods disguised as simple attribute access) and @dataclass (from the dataclasses module) for reducing boilerplate code when creating classes primarily meant to store data.