[Python OOP] 2. Deep Dive into Classes and Objects

03 Nov 2023 By Code Bricks

2. Deep Dive into Classes and Objects

Understanding classes and objects at a deeper level will allow you to write more efficient, maintainable, and robust object-oriented code in Python. In this section, we will explore some advanced topics related to class attributes, methods, constructors, and destructors.

2.1 Advanced Class Attributes

2.1.1 Class Attributes vs. Instance Attributes

Class attributes are shared by all instances of the class. They’re defined within the class but outside any methods.

class Dog:
    species = "Canis familiaris"  # Class attribute

    def __init__(self, name, age):
        self.name = name            # Instance attribute
        self.age = age              # Instance attribute

Instance attributes are unique to each object (instance of the class); they’re defined within the __init__ method.

2.1.2 Using @property

The @property decorator allows you to define methods that can be accessed like attributes, which is useful for encapsulation.

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:
            self._radius = value
        else:
            raise ValueError("Radius must be non-negative")

2.2 Advanced Methods

2.2.1 Class Methods

A class method takes cls as the first parameter and can access class variables and modify class state.

class Employee:
    num_employees = 0

    @classmethod
    def add_employee(cls):
        cls.num_employees += 1

Employee.add_employee()
print(Employee.num_employees)  # Output: 1

2.2.2 Static Methods

Static methods don’t take the instance (self) or class (cls) as the first parameter. They behave like regular functions but belong to the class’s namespace.

class MathUtility:
    @staticmethod
    def add(x, y):
        return x + y

print(MathUtility.add(2, 3))  # Output: 5

2.3 Understanding __init__ and __new__

The __init__ method is the initializer that gets called after the object has been created, to set up instance-specific attributes.

The __new__ method is the actual object constructor and is called first. It’s responsible for returning a new instance of your class.

class MyClass:
    def __new__(cls):
        print("Creating instance...")
        instance = super().__new__(cls)
        return instance

    def __init__(self):
        print("Initializing instance...")

obj = MyClass()

2.4 Handling Object Deletion with __del__

The __del__ method is the destructor. It is called when the instance is about to be destroyed and can be useful for resource cleanup.

class Resource:
    def __del__(self):
        print("Resource being released...")

res = Resource()
del res  # Output: Resource being released...

Keep in mind that in Python, the garbage collector handles memory management, so __del__ is not as commonly used as constructors.