DEV Community

Cover image for Python, Classes and Objects
Harsh Mishra
Harsh Mishra

Posted on

Python, Classes and Objects

Classes and Objects in Python

In Python, classes and objects are fundamental to object-oriented programming (OOP), offering a way to structure code into reusable components and define behaviors.

Defining Classes

Syntax for Defining a Class

To define a class in Python, use the class keyword followed by the class name and a colon :. Inside the class block, you define attributes (data) and methods (functions).

class MyClass:
    # Class body
    pass  # Placeholder for class definition
Enter fullscreen mode Exit fullscreen mode

In this example:

  • MyClass is the name of the class.
  • The pass statement is a placeholder indicating that the class body is currently empty.

This syntax sets up the blueprint for creating instances (objects) of the class MyClass, which can then possess attributes and methods defined within the class.

Attributes in Python Classes

Attributes in Python classes are used to store data associated with instances (objects) of the class. They can be broadly categorized into instance attributes and class attributes, each serving distinct purposes within the class.

Instance Attributes

Instance attributes are specific to each instance of a class. They are defined within the class's methods, especially within the __init__ method, and are accessed using the self keyword.

Syntax for Instance Attributes
class MyClass:
    def __init__(self, name, age):
        self.name = name  # Instance attribute
        self.age = age  # Instance attribute
Enter fullscreen mode Exit fullscreen mode

In this example:

  • name and age are instance attributes.
  • They are initialized when an instance of MyClass is created, and each instance (self) holds its own values for these attributes.

Class Attributes

Class attributes are shared among all instances of a class. They are defined at the class level outside of any instance methods, typically before the __init__ method, and are accessed using the class name or self within instance methods.

Syntax for Class Attributes
class MyClass:
    class_attribute = "Class Attribute Value"

    def __init__(self, instance_attribute):
        self.instance_attribute = instance_attribute
Enter fullscreen mode Exit fullscreen mode

In this example:

  • class_attribute is a class attribute shared by all instances of MyClass.
  • It is accessed using MyClass.class_attribute or self.class_attribute within instance methods.

Static Properties

In Python, there isn't a direct concept of "static properties" like in some other languages. However, you can achieve similar behavior using class attributes and static methods. Class attributes act as static properties that are shared among all instances of a class.

Example of Using Class Attributes as Static Properties

class MyClass:
    static_property = "Static Property Value"

    @staticmethod
    def get_static_property():
        return MyClass.static_property

    @staticmethod
    def set_static_property(value):
        MyClass.static_property = value
Enter fullscreen mode Exit fullscreen mode

In this example:

  • static_property is a class attribute acting as a static property.
  • get_static_property() and set_static_property(value) are static methods used to get and set the value of static_property.

Methods in Python Classes

Methods in Python classes are functions defined within the class and are used to define behaviors associated with instances (objects) of the class. They can be categorized into instance methods, class methods, and static methods, each serving different purposes within the class.

Instance Methods

Instance methods are the most common type of methods in Python classes. They operate on instances of the class and have access to instance attributes through the self parameter.

Syntax for Instance Methods
class MyClass:
    def __init__(self, name):
        self.name = name

    def instance_method(self):
        return f"Hello, my name is {self.name}"
Enter fullscreen mode Exit fullscreen mode

In this example:

  • instance_method is an instance method defined within MyClass.
  • It takes self as the first parameter, allowing access to instance attributes like self.name.

Class Methods

Class methods operate on the class itself rather than instances. They are defined using the @classmethod decorator and take cls as the first parameter, allowing access to class-level attributes.

Syntax for Class Methods
class MyClass:
    class_attribute = "Class Attribute Value"

    @classmethod
    def class_method(cls):
        return f"Class attribute value: {cls.class_attribute}"
Enter fullscreen mode Exit fullscreen mode

In this example:

  • class_method is a class method defined using @classmethod.
  • It takes cls as the first parameter, allowing access to MyClass's class attributes like cls.class_attribute.

Static Methods

Static methods do not operate on instance or class state. They are defined using the @staticmethod decorator and do not take self or cls as parameters. They are primarily used for utility functions related to the class.

Syntax for Static Methods
class MyClass:
    @staticmethod
    def static_method():
        return "This is a static method"
Enter fullscreen mode Exit fullscreen mode

In this example:

  • static_method is a static method defined using @staticmethod.
  • It does not take self or cls as parameters and operates independently of instance or class state.

Example of Using Methods

class MyClass:
    def __init__(self, name):
        self.name = name

    def instance_method(self):
        return f"Hello, my name is {self.name}"

    @classmethod
    def class_method(cls):
        return f"Class method called"

    @staticmethod
    def static_method():
        return "Static method called"
Enter fullscreen mode Exit fullscreen mode

In this example:

  • MyClass defines an instance_method, class_method, and static_method.
  • Each method serves a distinct purpose: instance_method interacts with instance-specific data, class_method interacts with class-level data, and static_method operates independently of instance or class state.

Creating Objects in Python

Creating objects in Python involves instantiating (creating instances of) classes. Each object, or instance, can be initialized with or without using a constructor method (__init__ method) to define its initial state.

Instantiating Objects from Classes

To create an object from a class in Python, you call the class name followed by parentheses (). This invokes the class constructor to create a new instance (object) of that class.

Example of Creating Objects Without Constructors
class MyClass:
    name = "Default"

# Creating objects (instances) of MyClass
obj1 = MyClass()
obj2 = MyClass()

# Accessing class property
print(obj1.name)  # Output: Default
print(obj2.name)  # Output: Default
Enter fullscreen mode Exit fullscreen mode

In this example:

  • MyClass is a class with a name class attribute.
  • obj1 and obj2 are instances (objects) of MyClass, each inheriting the name attribute from the class.

Constructor and Initializing Objects with Constructors (__init__ Method)

In Python, the constructor method (__init__) is used to initialize objects when they are created from a class. It is automatically called every time a new instance (object) of the class is instantiated.

Constructor (__init__ Method)

The __init__ method is a special method in Python classes that initializes (sets up) an object's initial state. It is commonly used to initialize instance attributes (properties) of the object.

Syntax of __init__ Method
class ClassName:
    def __init__(self, parameter1, parameter2, ...):
        self.attribute1 = parameter1
        self.attribute2 = parameter2
        # Additional initialization code
Enter fullscreen mode Exit fullscreen mode
  • self: Represents the instance of the class. It is used to access and modify instance attributes within the class.
  • parameter1, parameter2, ...: Parameters passed to the constructor when creating an object.
  • self.attribute1, self.attribute2: Instance attributes initialized with values from the constructor parameters.

Initializing Objects with Constructors

When an object is created from a class, Python automatically calls the __init__ method to initialize the object's state.

Example of Initializing Objects with Constructors
class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

# Creating objects (instances) of Product
product1 = Product("Laptop", 1200)
product2 = Product("Mouse", 30)

# Accessing object properties
print(product1.name, product1.price)  # Output: Laptop 1200
print(product2.name, product2.price)  # Output: Mouse 30
Enter fullscreen mode Exit fullscreen mode

In this example:

  • Product is a class with an __init__ method that takes name and price parameters.
  • product1 and product2 are instances (objects) of Product, each initialized with specific name and price values.
  • The __init__ method initializes self.name and self.price attributes for each instance based on the parameters passed during object creation.

Accessing and Modifying Instance Variables and Methods in Python

In Python object-oriented programming, accessing and modifying instance variables (attributes) and methods are fundamental operations when working with classes and objects. Instance variables store data unique to each instance (object) of a class, while methods define behaviors or actions that instances can perform.

Accessing Instance Variables

Instance variables are accessed using dot notation (object.variable_name). They represent the state or properties of each individual object.

Example of Accessing Instance Variables
class Car:
    def __init__(self, make, model):
        self.make = make
        self.model = model

# Creating an instance (object) of Car
my_car = Car("Toyota", "Camry")

# Accessing instance variables
print(my_car.make)   # Output: Toyota
print(my_car.model)  # Output: Camry
Enter fullscreen mode Exit fullscreen mode

In this example:

  • make and model are instance variables of the Car class.
  • my_car.make and my_car.model access the values of these variables for the my_car instance.

Modifying Instance Variables

Instance variables can be modified directly using assignment (object.variable_name = new_value).

Example of Modifying Instance Variables
class Car:
    def __init__(self, make, model):
        self.make = make
        self.model = model

# Creating an instance (object) of Car
my_car = Car("Toyota", "Camry")

# Modifying instance variables
my_car.make = "Honda"
my_car.model = "Accord"

# Accessing modified instance variables
print(my_car.make)   # Output: Honda
print(my_car.model)  # Output: Accord
Enter fullscreen mode Exit fullscreen mode

In this example:

  • After creating my_car instance with initial values "Toyota" and "Camry", we modify make to "Honda" and model to "Accord".
  • Subsequent accesses (print statements) show the updated values of make and model.

Accessing and Modifying Methods

Methods in Python classes define behaviors that instances can perform. They can access and modify instance variables through the self parameter.

Example of Accessing and Modifying Methods
class Car:
    def __init__(self, make, model):
        self.make = make
        self.model = model

    def display_info(self):
        print(f"Car make: {self.make}, model: {self.model}")

    def update_model(self, new_model):
        self.model = new_model

# Creating an instance (object) of Car
my_car = Car("Toyota", "Camry")

# Accessing methods
my_car.display_info()  # Output: Car make: Toyota, model: Camry

# Modifying instance variable through method
my_car.update_model("Corolla")

# Displaying updated information
my_car.display_info()  # Output: Car make: Toyota, model: Corolla
Enter fullscreen mode Exit fullscreen mode

In this example:

  • The Car class defines display_info() to print car make and model, and update_model(new_model) to modify the model instance variable.
  • my_car.display_info() displays initial values "Toyota" and "Camry".
  • my_car.update_model("Corolla") modifies model to "Corolla", reflected in subsequent display_info() call.

Accessing and Modifying Class Variables and Methods in Python

In Python, class variables and methods provide functionality and data that are shared across all instances of a class. They allow for centralized data management and behaviors that are not tied to any specific instance but rather to the class itself. Here's a comprehensive overview of accessing and modifying class variables, class methods, and static methods in Python.

Class Variables

Class variables are variables that are shared among all instances of a class. They are defined within the class but outside of any instance method.

Example of Class Variables
class Car:
    num_wheels = 4  # Class variable

    def __init__(self, make, model):
        self.make = make
        self.model = model

# Accessing class variable through class
print(Car.num_wheels)  # Output: 4

# Accessing class variable through instance
my_car = Car("Toyota", "Camry")
print(my_car.num_wheels)  # Output: 4
Enter fullscreen mode Exit fullscreen mode

In this example:

  • num_wheels is a class variable defined within the Car class.
  • The __init__ method is the constructor, initializing instance variables make and model.
  • Both Car.num_wheels and my_car.num_wheels access the same class variable.

Modifying Class Variables

Class variables can be modified using either the class name or any instance of the class.

Example of Modifying Class Variables
class Car:
    num_wheels = 4  # Class variable

    def __init__(self, make, model):
        self.make = make
        self.model = model

# Modifying class variable through class
Car.num_wheels = 6
print(Car.num_wheels)  # Output: 6

# Modifying class variable through instance
my_car = Car("Toyota", "Camry")
my_car.num_wheels = 5
print(my_car.num_wheels)  # Output: 5
print(Car.num_wheels)    # Output: 6 (class variable remains unchanged)
Enter fullscreen mode Exit fullscreen mode

In this example:

  • Car.num_wheels is modified to 6 directly.
  • my_car.num_wheels is modified to 5, creating an instance variable that shadows the class variable for that instance only.

Class Methods

Class methods are methods that are bound to the class rather than its instances. They can access and modify class variables.

Example of Class Methods
class Car:
    num_wheels = 4  # Class variable

    def __init__(self, make, model):
        self.make = make
        self.model = model

    @classmethod
    def update_wheels(cls, num):
        cls.num_wheels = num

# Calling class method through class
Car.update_wheels(6)
print(Car.num_wheels)  # Output: 6

# Calling class method through instance
my_car = Car("Toyota", "Camry")
my_car.update_wheels(5)
print(my_car.num_wheels)  # Output: 5
print(Car.num_wheels)     # Output: 5 (class variable updated)
Enter fullscreen mode Exit fullscreen mode

In this example:

  • update_wheels(cls, num) is a class method defined with @classmethod.
  • Both Car.update_wheels(6) and my_car.update_wheels(5) modify the num_wheels class variable.

Static Methods

Static methods in Python are methods that do not operate on instance or class state. They are defined using @staticmethod and can be accessed through both the class and its instances.

Example of Static Methods
class Car:
    def __init__(self, make, model):
        self.make = make
        self.model = model

    @staticmethod
    def make_sound():
        print("Vroom!")

# Calling static method through class
Car.make_sound()  # Output: Vroom!

# Calling static method through instance
my_car = Car("Toyota", "Camry")
my_car.make_sound()  # Output: Vroom!
Enter fullscreen mode Exit fullscreen mode

In this example:

  • make_sound() is a static method defined with @staticmethod.
  • It does not require self or cls parameters and can be called using both the class name (Car.make_sound()) and instance (my_car.make_sound()).

Encapsulation in Python

Encapsulation is one of the fundamental principles of object-oriented programming (OOP) that bundles data (attributes) and methods (functions) into a single unit called a class. It allows you to restrict access to certain components of the object, promoting data hiding and abstraction.

Encapsulation and Data Hiding

Encapsulation helps in achieving data hiding, which means that the internal state of an object is hidden from the outside world. Only the object itself can directly interact with its internal state. This prevents external code from directly accessing or modifying sensitive data, promoting better security and maintainability of code.

Using Private and Protected Access Specifiers

In Python, encapsulation and access control are managed through naming conventions rather than strict access specifiers. However, Python provides conventions to indicate the visibility of attributes and methods:

  1. Private Members: Attributes and methods that are intended to be private are prefixed with double underscores (__). Python uses name mangling to make these attributes and methods harder to access from outside the class.

Example:

   class MyClass:
       def __init__(self):
           self.__private_attr = 10

       def __private_method(self):
           return "This is a private method"

   obj = MyClass()
   # Accessing private attribute (not recommended)
   # print(obj.__private_attr)  # This would raise an AttributeError

   # Accessing private method (not recommended)
   # print(obj.__private_method())  # This would raise an AttributeError
Enter fullscreen mode Exit fullscreen mode

Note: Although Python allows accessing private members in a roundabout way (obj._MyClass__private_attr), it's generally discouraged to do so to maintain encapsulation.

  1. Protected Members: Attributes and methods that are intended to be protected are prefixed with a single underscore (_). This indicates to developers that these members are not intended for use outside the class, but there is no strict enforcement by the Python interpreter.

Example:

   class MyClass:
       def __init__(self):
           self._protected_attr = 20

       def _protected_method(self):
           return "This is a protected method"

   obj = MyClass()
   # Accessing protected attribute
   print(obj._protected_attr)  # Output: 20

   # Accessing protected method
   print(obj._protected_method())  # Output: This is a protected method
Enter fullscreen mode Exit fullscreen mode

While these attributes and methods can be accessed directly, their leading underscore signals to other developers that they are part of the class's implementation and should be treated as protected.

Inheritance in Python

Inheritance is a key concept in object-oriented programming (OOP) that allows a new class (derived class) to inherit attributes and methods from an existing class (base class). This promotes code reuse and allows for hierarchical relationships between classes.

Extending Classes: Base Class and Derived Class

In Python, inheritance is defined using the syntax class DerivedClassName(BaseClassName):, where DerivedClassName is the new class inheriting from BaseClassName. The derived class inherits all attributes and methods from the base class unless explicitly overridden.

Example of Inheritance
class Animal:
    def __init__(self, species):
        self.species = species

    def make_sound(self):
        pass  # Placeholder method

class Dog(Animal):
    def __init__(self, name):
        super().__init__("Dog")  # Calling base class constructor
        self.name = name

    def make_sound(self):
        return "Woof!"

# Creating instances of derived class
dog = Dog("Buddy")
print(dog.species)    # Output: Dog (inherited from base class)
print(dog.make_sound())  # Output: Woof! (overridden method)
Enter fullscreen mode Exit fullscreen mode

In this example:

  • Animal is the base class with an attribute species and a method make_sound.
  • Dog is the derived class inheriting from Animal.
  • super().__init__("Dog") calls the constructor of the base class Animal and initializes the species attribute.
  • make_sound method is overridden in Dog class to provide specific behavior for dogs.

Overriding Methods in Derived Classes

Derived classes can override methods from the base class to provide specialized implementations while retaining the same method signature. This allows flexibility in adapting behavior inherited from the base class.

Example of Method Overriding
class Animal:
    def make_sound(self):
        return "Generic animal sound"

class Dog(Animal):
    def make_sound(self):
        return "Woof!"

class Cat(Animal):
    def make_sound(self):
        return "Meow!"

# Polymorphism: using overridden methods
dog = Dog()
cat = Cat()

print(dog.make_sound())  # Output: Woof!
print(cat.make_sound())  # Output: Meow!
Enter fullscreen mode Exit fullscreen mode

In this example:

  • Both Dog and Cat classes inherit from Animal class.
  • They override the make_sound method to provide specific sounds for dogs and cats.

super() Function

The super() function in Python is used to call methods from the base class within the derived class. It allows accessing and invoking the methods and constructors of the base class, facilitating method overriding and cooperative multiple inheritance.

Example of Using super()
class Animal:
    def __init__(self, species):
        self.species = species

    def show_info(self):
        print(f"I am a {self.species}")

class Dog(Animal):
    def __init__(self, name):
        super().__init__("Dog")
        self.name = name

    def show_info(self):
        super().show_info()
        print(f"My name is {self.name}")

# Using super() to call base class methods
dog = Dog("Buddy")
dog.show_info()
Enter fullscreen mode Exit fullscreen mode

In this example:

  • Dog class calls super().__init__("Dog") to invoke the constructor of the base class Animal.
  • super().show_info() is used in Dog class to call the show_info method of the base class, followed by printing the dog's name.

Here's a comprehensive guide covering some of the important magic methods (special methods) in Python:

Python Magic Methods Guide

Object Initialization and Representation

__init__

  • Initializes an object when instantiated.
  • Syntax: def __init__(self, ...)
class MyClass:
    def __init__(self, value):
        self.value = value

obj = MyClass(10)
print(obj.value)  # Output: 10
Enter fullscreen mode Exit fullscreen mode

__str__

  • Returns the informal or nicely printable string representation of an object.
  • Syntax: def __str__(self)
class MyClass:
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return f"MyClass object with value: {self.value}"

obj = MyClass(10)
print(str(obj))  # Output: MyClass object with value: 10
Enter fullscreen mode Exit fullscreen mode

__repr__

  • Returns the official string representation of an object. Used for debugging and logging.
  • Syntax: def __repr__(self)
class MyClass:
    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return f"MyClass({self.value})"

obj = MyClass(10)
print(repr(obj))  # Output: MyClass(10)
Enter fullscreen mode Exit fullscreen mode

Comparison Operators

__eq__

  • Checks equality between two objects.
  • Syntax: def __eq__(self, other)
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2)  # Output: True
Enter fullscreen mode Exit fullscreen mode

__lt__, __le__, __gt__, __ge__

  • Less than, less than or equal to, greater than, and greater than or equal to comparison methods respectively.
  • Syntax: def __lt__(self, other), def __le__(self, other), def __gt__(self, other), def __ge__(self, other)
class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade

    def __lt__(self, other):
        return self.grade < other.grade

    def __le__(self, other):
        return self.grade <= other.grade

    def __gt__(self, other):
        return self.grade > other.grade

    def __ge__(self, other):
        return self.grade >= other.grade

s1 = Student("Alice", 85)
s2 = Student("Bob", 90)
print(s1 < s2)  # Output: True
Enter fullscreen mode Exit fullscreen mode

Arithmetic Operators

__add__, __sub__, __mul__, __truediv__, __floordiv__, __mod__

  • Addition, subtraction, multiplication, true division, floor division, and modulo operations respectively.
  • Syntax: def __add__(self, other), def __sub__(self, other), def __mul__(self, other), def __truediv__(self, other), def __floordiv__(self, other), def __mod__(self, other)
class Number:
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return self.value + other.value

    def __sub__(self, other):
        return self.value - other.value

    def __mul__(self, other):
        return self.value * other.value

    def __truediv__(self, other):
        return self.value / other.value

    def __floordiv__(self, other):
        return self.value // other.value

    def __mod__(self, other):
        return self.value % other.value

num1 = Number(10)
num2 = Number(5)
print(num1 + num2)  # Output: 15
Enter fullscreen mode Exit fullscreen mode

Container Methods

__len__

  • Returns the length of an object.
  • Syntax: def __len__(self)
class MyList:
    def __init__(self, items):
        self.items = items

    def __len__(self):
        return len(self.items)

my_list = MyList([1, 2, 3, 4])
print(len(my_list))  # Output: 4
Enter fullscreen mode Exit fullscreen mode

__getitem__, __setitem__, __delitem__

  • Getter, setter, and deleter methods for accessing and modifying items using index or key.
  • Syntax: def __getitem__(self, key), def __setitem__(self, key, value), def __delitem__(self, key)
class MyDict:
    def __init__(self, items):
        self.items = items

    def __getitem__(self, key):
        return self.items[key]

    def __setitem__(self, key, value):
        self.items[key] = value

    def __delitem__(self, key):
        del self.items[key]

my_dict = MyDict({'a': 1, 'b': 2})
print(my_dict['a'])  # Output: 1
my_dict['c'] = 3
print(my_dict['c'])  # Output: 3
del my_dict['b']
print(my_dict.items)  # Output: {'a': 1, 'c': 3}
Enter fullscreen mode Exit fullscreen mode

Callable Objects

__call__

  • Enables the instance of a class to be called as a function.
  • Syntax: def __call__(self, *args, **kwargs)
class Multiplier:
    def __init__(self, factor):
        self.factor = factor

    def __call__(self, x):
        return self.factor * x

mul = Multiplier(5)
result = mul(10)
print(result)  # Output: 50
Enter fullscreen mode Exit fullscreen mode

Type Conversion

  • __int__(self): Convert to an integer (int()).
  • __float__(self): Convert to a float (float()).
  • __complex__(self): Convert to a complex number (complex()).
  • __bytes__(self): Convert to a bytes object (bytes()).
  • __index__(self): Called for integer indexing (obj[idx]).

Example:

class Number:
    def __init__(self, value):
        self.value = value

    def __int__(self):
        return int(self.value)

    def __float__(self):
        return float(self.value)

    def __complex__(self):
        return complex(self.value, 0)

    def __bytes__(self):
        return bytes(str(self.value), 'utf-8')

    def __index__(self):
        return int(self.value)

num = Number(42)
print(int(num))        # Output: 42
print(float(num))      # Output: 42.0
print(complex(num))    # Output: (42+0j)
print(bytes(num))      # Output: b'42'
print('{:x}'.format(num.__index__()))  # Output: 2a
Enter fullscreen mode Exit fullscreen mode

Boolean Conversion

  • __bool__(self): Boolean value of the object (bool()).

Example:

class Person:
    def __init__(self, name):
        self.name = name

    def __bool__(self):
        # Returns False if name is empty, True otherwise
        return bool(self.name)

person1 = Person("Alice")
person2 = Person("")

print(bool(person1))  # Output: True
print(bool(person2))  # Output: False
Enter fullscreen mode Exit fullscreen mode

Top comments (0)