Python

Object-Oriented Programming in Python: Classes, Objects, and Inheritance

Welcome to our comprehensive guide on Object-Oriented Programming (OOP) in Python! Whether you’re a beginner or looking to deepen your understanding of Python programming, this tutorial will demystify core OOP concepts such as Classes, Objects, and Inheritance. By the end of this article, you’ll have a solid grasp of creating and managing Python Classes and Objects, and you’ll see practical examples that illustrate these principles. Dive into the world of Python OOP and elevate your programming skills to the next level!

Introduction to Object-Oriented Programming in Python

Object-Oriented Programming (OOP) in Python is a programming paradigm that uses classes and objects to create models based on the real world environment. It encourages modular design and code reusability, making it easier to manage and maintain large-scale applications. Python, being an object-oriented language, supports all the key features of OOP—such as encapsulation, inheritance, and polymorphism.

In Python, everything is an object, which means you can leverage OOP principles in numerous ways. Let’s dive into some core concepts.

Encapsulation

Encapsulation is the mechanism of hiding the internal data of an object and exposing only necessary parts. Python uses underscores to denote private fields and methods. For instance:

class Car:
    def __init__(self, make, model):
        self.make = make   # Public attribute
        self._model = model # Protected attribute - not enforced strictly but should be used within the class or subclasses

    def get_model(self):
        return self._model

my_car = Car("Toyota", "Corolla")
print(my_car.get_model())  # Output: Corolla

Inheritance

Inheritance allows a new class (known as a derived or child class) to inherit properties and methods from an existing class (known as a base or parent class). This helps in code reusability and establishing a hierarchical relationship between classes.

Here’s a Python inheritance example:

class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

dog = Dog("Buddy")
cat = Cat("Whiskers")

print(dog.speak())  # Output: Buddy says Woof!
print(cat.speak())  # Output: Whiskers says Meow!

Polymorphism

Polymorphism allows methods to do different things based on the object it is acting upon. It gives a way to use a single interface to represent different types.

class Bird:
    def fly(self):
        return "Fly with wings"

class Airplane:
    def fly(self):
        return "Fly with fuel"

def flying_test(entity):
    print(entity.fly())

bird = Bird()
airplane = Airplane()

flying_test(bird)       # Output: Fly with wings
flying_test(airplane)   # Output: Fly with fuel

Python’s dynamic nature and simplicity make it an excellent language for both beginners and experienced programmers when dealing with object-oriented concepts. The support for OOP allows you to create more modular, readable, and manageable code which is a vital aspect of modern software development.

For more detailed information on Python OOP, the Python documentation provides an extensive guide on classes and object-oriented programming.

Understanding Python Classes and Their Structure

In Python, classes are the blueprints from which objects are created. A class encapsulates data for the object and defines methods to manipulate that data. Let’s dive into the structure of Python classes and understand the essential components that make up a class.

Defining a Python Class

A class definition in Python begins with the keyword class, followed by the class name and a colon. The class name should adhere to the PascalCase convention, meaning each word starts with a capital letter and no underscores.

class MyClass:
    pass

The __init__ Method

The __init__ method, also known as the constructor, is a special method automatically called when a new instance of the class is created. It initializes the object’s attributes and can take arguments to set them.

class MyClass:
    def __init__(self, attribute1, attribute2):
        self.attribute1 = attribute1
        self.attribute2 = attribute2

When an instance is created, __init__ ensures the object has the necessary properties.

object1 = MyClass('value1', 'value2')

Instance Variables and Class Variables

Instance variables are unique to each instance and defined within the __init__ method using self. Class variables, on the other hand, are shared across all instances of the class and are defined directly within the class.

class MyClass:
    class_variable = "shared value"  # Class variable

    def __init__(self, instance_variable):
        self.instance_variable = instance_variable  # Instance variable

Instance variables are accessed using the self keyword from within the class.

Methods in Python Classes

Methods are functions defined within a class to perform operations using the object’s data. The first parameter of any method in a class is self, which represents the instance of the class.

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

    def method(self):
        return f"Attribute value is {self.attribute}"

Methods can perform various actions, from simple attribute manipulation to more complex operations.

Class and Static Methods

Python supports two additional types of methods: class methods and static methods. Class methods utilize the @classmethod decorator and take cls as the first parameter, representing the class. Static methods use the @staticmethod decorator and do not automatically pass the instance (self) or class (cls).

Class Method Example

class MyClass:
    class_variable = "shared value"
    
    def __init__(self, instance_value):
        self.instance_value = instance_value
    
    @classmethod
    def print_class_variable(cls):
        print(cls.class_variable)

Static Method Example

class MyClass:
    class_variable = "shared value"
    
    def __init__(self, instance_value):
        self.instance_value = instance_value
    
    @staticmethod
    def print_message():
        print("This is a static method.")

Inheritance in Python Classes

Inheritance enables a class to inherit attributes and methods from another class, promoting code reusability. A class that inherits from another class is known as a derived or child class, whereas the class being inherited from is the base or parent class.

class ParentClass:
    def parent_method(self):
        return "This is a method in the parent class"

class ChildClass(ParentClass):
    def child_method(self):
        return "This is a method in the child class"

To dive deeper into class declarations, object creation, and the nuances of object-oriented programming in Python, the Python Documentation on Classes provides a comprehensive guide.

Conclusion

Understanding the structure and syntax of classes is crucial for effective object-oriented programming in Python. Through the components discussed — from class variables to methods and inheritance — Python offers a robust framework for building modular and maintainable code.

Stay tuned for the next sections where we will delve into the nuances of creating and manipulating Python objects, exploring inheritance in depth, and applying OOP concepts with practical examples.

Creating and Manipulating Python Objects

Creating and Manipulating Python Objects involves understanding how to instantiate objects from classes and interact with these instances. In Python, objects are the central building blocks of Object-Oriented Programming (OOP). A Python class can be thought of as a blueprint for creating objects, and each unique object created from this blueprint is an instance of the class.

Instantiating Objects

To create an object in Python, you instantiate a class. This is done by calling the class as if it were a function.

class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species

# Creating an object of the Animal class
dog = Animal("Buddy", "Dog")
cat = Animal("Whiskers", "Cat")

In this example, Animal is a class with an __init__ method, which initializes the name and species attributes. By calling Animal("Buddy", "Dog"), we create an instance of the Animal class named dog and another instance named cat.

Accessing and Modifying Attributes

Once an object is created, you can access and modify its attributes using the dot notation.

print(dog.name)  # Output: Buddy
print(cat.species)  # Output: Cat

dog.name = "Max"
print(dog.name)  # Output: Max

Here, dog.name allows us to read the name attribute of the dog object, and cat.species gives us the species of the cat. We can also modify dog.name to change its value from “Buddy” to “Max”.

Object Methods

Classes can have methods that operate on the object’s attributes. These methods are defined similarly to regular functions but take self as the first parameter.

class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species
    def describe(self):
        return f"{self.name} is a {self.species}"

# Creating an object
dog = Animal("Buddy", "Dog")

# Calling the describe method
description = dog.describe()
print(description)  # Output: Buddy is a Dog

The describe method in the Animal class provides a way to return a formatted string containing the object’s name and species.

Dynamic Attribute Assignment

Python allows dynamically adding attributes to objects after they have been created.

dog.age = 5
print(dog.age)  # Output: 5

In this case, we assign an age attribute to the dog object after its creation.

Using the Built-in Functions for Objects

Python provides several built-in functions to interact with objects:

  • getattr(): Fetches the value of an attribute.
  • setattr(): Sets the value of an attribute.
  • delattr(): Deletes an attribute.
  • hasattr(): Checks if an object has a particular attribute.
# Using built-in functions
print(getattr(dog, 'name'))  # Output: Max
setattr(dog, 'age', 6)
print(dog.age)  # Output: 6
hasattr(dog, 'species')  # Output: True
delattr(dog, 'age')
hasattr(dog, 'age')  # Output: False

These functions are particularly useful when dealing with dynamic attributes or when the attribute names are stored in variables.

For more detailed information on Python classes and objects, refer to the official Python documentation on Classes and Data Model.

Exploring Python Inheritance: Principles and Examples

In Object-Oriented Programming (OOP), inheritance allows a class to inherit the attributes and methods from another class. This principle helps in reducing redundancy by promoting code reusability and enhancing maintainability. In Python, inheritance is fundamental for creating hierarchies and shared behaviors among different classes, facilitating the representation of relationships in the real world.

Basic Inheritance in Python

To define a new class inheriting attributes and methods from an existing class, we use the following syntax:

class ParentClass:
    def __init__(self, value):
        self.value = value
    
    def display_value(self):
        print(f"Value: {self.value}")

class ChildClass(ParentClass):
    pass

# Example Usage
parent = ParentClass(10)
parent.display_value()  # Output: Value: 10

child = ChildClass(20)
child.display_value()  # Output: Value: 20

Overriding Methods

Sometimes, you may want to modify the behavior of an inherited method in the child class. This is done by overriding the method:

class ParentClass:
    def greet(self):
        print("Hello from Parent!")

class ChildClass(ParentClass):
    def greet(self):
        print("Hello from Child!")

# Example Usage
parent = ParentClass()
parent.greet()  # Output: Hello from Parent!

child = ChildClass()
child.greet()  # Output: Hello from Child!

Calling Parent Class Methods

To invoke a parent class method within the overridden method in the child class, use the super() function, which returns a temporary object of the superclass:

class ParentClass:
    def greet(self):
        print("Hello from Parent!")

class ChildClass(ParentClass):
    def greet(self):
        super().greet()
        print("Hello from Child!")

# Example Usage
child = ChildClass()
child.greet()
# Output: 
# Hello from Parent!
# Hello from Child!

Multiple Inheritance

Python also supports multiple inheritance, allowing a child class to inherit from more than one parent class. This is useful when a class needs to inherit functionalities from multiple sources. Here is an example:

class ClassA:
    def method_a(self):
        print("Method A from Class A")

class ClassB:
    def method_b(self):
        print("Method B from Class B")

class ChildClass(ClassA, ClassB):
    pass

# Example Usage
child = ChildClass()
child.method_a()  # Output: Method A from Class A
child.method_b()  # Output: Method B from Class B

Practical Use Case: A Base and Derived Classes

Imagine a scenario in a software application where different types of employees need to be represented, but all share common attributes and behaviors. Inheritance can cleanly model this situation:

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary
    
    def work(self):
        print(f"{self.name} is working with a salary of {self.salary}")

class Manager(Employee):
    def work(self):
        super().work()
        print(f"{self.name} is managing a team")

class Developer(Employee):
    def work(self):
        super().work()
        print(f"{self.name} is writing code")

# Example Usage
manager = Manager("Alice", 80000)
developer = Developer("Bob", 60000)

manager.work()
# Output:
# Alice is working with a salary of 80000
# Alice is managing a team

developer.work()
# Output:
# Bob is working with a salary of 60000
# Bob is writing code

By employing inheritance, we encapsulate common functionality within the base class and extend or modify it in derived classes as necessary. This is a cornerstone of robust and scalable OOP design in Python.

For more detailed information and advanced topics on inheritance in Python, refer to the official Python documentation: Python Inheritance.

Applying OOP Concepts in Python with Practical Code Examples

To fully grasp Object-Oriented Programming (OOP) in Python, let’s delve into some practical code examples that apply key OOP concepts— namely, classes, objects, and inheritance.

Creating a Simple Class and Object

Let’s start by defining a straightforward class Car and creating an object from it.

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def display_info(self):
        return f"{self.year} {self.make} {self.model}"

# Create an object of the Car class
my_car = Car("Toyota", "Corolla", 2022)
print(my_car.display_info())

In this example, the Car class is defined with an __init__ method that initializes the object’s attributes. The display_info method provides a formatted string representation of the car’s details. By creating an instance (my_car) of Car, we can call display_info to retrieve and print the car details.

Implementing Inheritance

Inheritance allows us to build a new class using attributes and methods from an existing class. Here, we extend the Car class to create a ElectricCar class.

class ElectricCar(Car):
    def __init__(self, make, model, year, battery_size):
        super().__init__(make, model, year)
        self.battery_size = battery_size

    def display_battery_info(self):
        return f"{self.make} {self.model} has a {self.battery_size}-kWh battery."

# Create an object of the ElectricCar class
my_electric_car = ElectricCar("Tesla", "Model S", 2022, 100)
print(my_electric_car.display_info())
print(my_electric_car.display_battery_info())

Here, ElectricCar inherits from Car. The super().__init__ call ensures the parent class’s __init__ method is executed, initializing the attributes make, model, and year. Another method display_battery_info is added to deal specifically with electric cars. Creating an instance of ElectricCar and calling its methods demonstrates inheritance in action.

Utilizing Polymorphism

Polymorphism lets us use a unified interface for different data types. Below is an example showing polymorphism using classes Dog and Cat, both inheriting from a Pet class.

class Pet:
    def speak(self):
        pass

class Dog(Pet):
    def speak(self):
        return "Woof!"

class Cat(Pet):
    def speak(self):
        return "Meow!"

# Function to demonstrate polymorphism
def make_pet_speak(pet):
    print(pet.speak())

# Create objects of Dog and Cat classes
my_dog = Dog()
my_cat = Cat()

make_pet_speak(my_dog)
make_pet_speak(my_cat)

In this code, Dog and Cat override the speak method defined in the Pet class. The function make_pet_speak takes any Pet object and calls its speak method, demonstrating polymorphism.

Encapsulation with Property Methods

Encapsulation refers to bundling data and methods that operate on the data within one unit, e.g., a class, and restricting direct access to some of the object’s components.

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.__balance = balance

    @property
    def balance(self):
        return self.__balance

    @balance.setter
    def balance(self, amount):
        if amount < 0:
            print("Balance cannot be negative.")
        else:
            self.__balance = amount

# Create an object of BankAccount class
account = BankAccount("John Doe", 100)
print(account.balance)  # 100

account.balance = 200  # Update balance
print(account.balance)  # 200

account.balance = -50  # Attempt to set a negative balance

In the BankAccount class, the __balance attribute is private and can only be accessed or modified via property methods. The @property decorator makes balance act like an attribute, while the @balance.setter ensures any changes to the balance are validated.

Using these practical examples, you can see how fundamental OOP concepts like classes, objects, inheritance, polymorphism, and encapsulation are implemented in Python. For further details, you can refer to the official Python documentation on classes.

Advanced Python Object-Oriented Tutorial: Best Practices and Design Patterns

In designing robust and maintainable software using Object-Oriented Programming (OOP) in Python, adopting best practices and leveraging design patterns can significantly enhance code quality. This section delves into advanced OOP techniques, providing Python programming experts with insights into superior coding standards and reusable design strategies.

Best Practices

  1. Encapsulation and Data Hiding:
    Encapsulation is fundamental in OOP to restrict access to an object’s internals. In Python, you can use single or double underscores to denote private or protected attributes.

    class BankAccount:
        def __init__(self, balance):
            self.__balance = balance  # Private attribute
    
        def deposit(self, amount):
            if amount > 0:
                self.__balance += amount
    
        def get_balance(self):
            return self.__balance
    
    account = BankAccount(1000)
    account.deposit(500)
    print(account.get_balance())  # Output: 1500
    # print(account.__balance)  # Raises an AttributeError
    
  2. Using Properties for Controlled Access:
    Properties allow managing access to private variables in a clean manner.

    class Temperature:
        def __init__(self, fahrenheit):
            self._fahrenheit = fahrenheit
    
        @property
        def fahrenheit(self):
            return self._fahrenheit
    
        @fahrenheit.setter
        def fahrenheit(self, value):
            if value < -459.67:
                raise ValueError("Temperature below -459.67°F is not physically possible.")
            self._fahrenheit = value
    
    temp = Temperature(32)
    print(temp.fahrenheit)  # Output: 32
    temp.fahrenheit = 451  # Updating via setter
    print(temp.fahrenheit)  # Output: 451
    
  3. Follow the Single Responsibility Principle (SRP):
    Ensure each class has one responsibility or reason to change.

    class Order:
        def __init__(self, items):
            self.items = items
    
        def calculate_total(self):
            return sum(item.price for item in self.items)
    
    class OrderPrinter:
        @staticmethod
        def print_order(order):
            for item in order.items:
                print(f'Item: {item.name}, Price: {item.price}')
    
    # Correct separation of concerns
    order = Order([Item('Laptop', 999.99), Item('Mouse', 25.50)])
    OrderPrinter.print_order(order)
    

Design Patterns

  1. Singleton Pattern:
    Ensures a class has only one instance and provides a global point of access to it.

    class Singleton:
        _instance = None
    
        def __new__(cls, *args, **kwargs):
            if not cls._instance:
                cls._instance = super().__new__(cls, *args, **kwargs)
            return cls._instance
    
    singleton1 = Singleton()
    singleton2 = Singleton()
    print(singleton1 is singleton2)  # Output: True
    
  2. Factory Pattern:
    Provides an interface for creating objects in a super class but allows subclasses to alter the type of objects that will be created.

    class AnimalFactory:
        @staticmethod
        def create_animal(animal_type):
            if animal_type == 'Dog':
                return Dog()
            elif animal_type == 'Cat':
                return Cat()
            else:
                raise ValueError('Unknown animal type')
    
    class Dog:
        def speak(self):
            return "Woof!"
    
    class Cat:
        def speak(self):
            return "Meow!"
    
    dog = AnimalFactory.create_animal('Dog')
    print(dog.speak())  # Output: Woof!
    
  3. Observer Pattern:
    A behavioral pattern where an object, called the subject, maintains a list of dependencies called observers that are notified of state changes.

    class Subject:
        def __init__(self):
            self._observers = []
    
        def attach(self, observer):
            self._observers.append(observer)
    
        def detach(self, observer):
            self._observers.remove(observer)
    
        def notify(self):
            for observer in self._observers:
                observer.update(self)
    
    class ConcreteObserver:
        def update(self, subject):
            print(f'{self.__class__.__name__}: Reacted to {subject.__class__.__name__} change.')
    
    subject = Subject()
    observer_a = ConcreteObserver()
    observer_b = ConcreteObserver()
    subject.attach(observer_a)
    subject.attach(observer_b)
    subject.notify()  # Output: ConcreteObserver: Reacted to Subject change.
                      #         ConcreteObserver: Reacted to Subject change.
    

By adhering to these best practices and design patterns, Python developers can enhance the robustness, maintainability, and scalability of their object-oriented applications. For more information, refer to the official Python documentation.

Common Pitfalls and Mistakes in OOP Programming with Python

In working with Object-Oriented Programming in Python, beginners and even experienced developers can encounter common pitfalls and mistakes. Here, we delve into several of these issues to provide clarity and guidance that will help avoid them.

  1. Misuse of Inheritance:
    Inheritance is one of the core aspects of OOP (Object-Oriented Programming), yet it is often misused. Developers may force class inheritance when a composition or interface would be more appropriate. This practice can lead to confusing and tightly coupled codebase that is difficult to maintain.

    Common Mistake:

    class Animal:
        def __init__(self, name):
            self.name = name
    
    class Dog(Animal):
        def __init__(self, name, breed):
            super().__init__(name)
            self.breed = breed
    
    class Fish(Dog):  # This is inappropriate inheritance
        def __init__(self, name, breed, water_type):
            super().__init__(name, breed)
            self.water_type = water_type
    

    Preferred Approach:
    Use composition over inheritance where suitable.

    class Animal:
        def __init__(self, name):
            self.name = name
    
    class Dog:
        def __init__(self, name, breed):
            self.animal = Animal(name)
            self.breed = breed
    
    class Fish:
        def __init__(self, name, water_type):
            self.animal = Animal(name)
            self.water_type = water_type
    
  2. Improper Use of Class Attributes vs. Instance Attributes:
    Another frequent mistake is the improper usage of class attributes and instance attributes. This can inadvertently lead to shared state across all instances of a class when the intent was to have instance-specific state.

    Common Mistake:

    class MyClass:
        items = []  # This is a class attribute
    
        def add_item(self, item):
            self.items.append(item)
    
    obj1 = MyClass()
    obj2 = MyClass()
    obj1.add_item("apple")
    print(obj2.items)  # Outputs: ["apple"]
    

    Preferred Approach:
    Correct use of instance attributes.

    class MyClass:
        def __init__(self):
            self.items = []  # This is an instance attribute
    
        def add_item(self, item):
            self.items.append(item)
    
    obj1 = MyClass()
    obj2 = MyClass()
    obj1.add_item("apple")
    print(obj2.items)  # Outputs: []
    
  3. Overcomplicating Code with Unnecessary OOP Features:
    It’s critical to remember that just because OOP features are available, doesn’t mean they need to be utilized in every scenario. Overcomplicating code with unnecessary inheritance, multiple levels of abstraction, or too many classes can hinder readability and maintenance.

    Common Mistake:

    class Engine:
        pass
    
    class CarWithEngine:
        def __init__(self, engine):
            self.engine = engine
    
        def start(self):
            print("Engine starts")
    
    class Car(CarWithEngine):
        def __init__(self, engine):
            super().__init__(engine)
    
    car = Car(Engine())
    car.start()  # Simple functionality, but overcomplicated inheritance
    

    Preferred Approach:
    Keep it simple when OOP features are not necessary.

    class Car:
        def __init__(self, engine):
            self.engine = engine
    
        def start(self):
            print("Engine starts")
    
    my_car = Car(Engine())
    my_car.start()  # Clear and straightforward approach
    
  4. Failing to Use super() Properly:
    When calling a parent class’s method in a subclass, it’s crucial to use super() correctly. Failing to do so can lead to issues with the method resolution order (MRO) and might not execute the intended superclass methods.

    Common Mistake:

    class Parent:
        def __init__(self):
            print("Parent init")
    
    class Child(Parent):
        def __init__(self):
            Parent.__init__(self)  # Directly calling Parent's init method
            print("Child init")
    
    child = Child()  # This can lead to errors in certain multi-inheritance scenarios
    

    Preferred Approach:
    Use super() to correctly manage the MRO.

    class Parent:
        def __init__(self):
            print("Parent init")
    
    class Child(Parent):
        def __init__(self):
            super().__init__()  # Proper usage of super()
            print("Child init")
    
    child = Child()
    

Developing Python Applications with an Object-Oriented Approach

When developing Python applications with an Object-Oriented approach, it’s crucial to leverage the inherent features of Python such as classes, objects, and inheritance to create modular, reusable, and maintainable code. This section explores how to structure your codebase using OOP principles, focusing on the creation of classes and objects, as well as the implementation of inheritance to extend functionality.

Designing Classes

Begin by designing your classes to represent the key components of your application. Classes in Python are defined using the class keyword. For instance, consider you’re developing an e-commerce application, and you need a class to represent a product:

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def display_info(self):
        print(f"Product Name: {self.name} - Price: ${self.price}")

Instantiating Objects

Create instances (objects) of the Product class to represent individual items in your inventory. Objects are instances of classes, and they hold the attributes and behaviors defined in their respective classes.

product1 = Product("Laptop", 1200)
product2 = Product("Smartphone", 800)

product1.display_info()  # Output: Product Name: Laptop - Price: $1200
product2.display_info()  # Output: Product Name: Smartphone - Price: $800

Implementing Inheritance

Inheritance allows you to define a new class that is a modified version of an existing class. This is particularly useful when building complex applications where some entities share common features. In the e-commerce application, you might have different types of products, each with additional specific attributes.

class Electronic(Product):
    def __init__(self, name, price, warranty_period):
        super().__init__(name, price)  # Inherit attributes and methods from Product
        self.warranty_period = warranty_period

    def display_info(self):
        super().display_info()
        print(f"Warranty Period: {self.warranty_period} years")

Using Inheritance

Create instances of the Electronic class to demonstrate inheritance in action:

electronic1 = Electronic("Laptop", 1200, 2)
electronic1.display_info()

# Output:
# Product Name: Laptop - Price: $1200
# Warranty Period: 2 years

Best Practices

  1. Encapsulation: Keep the internal state of objects hidden and provide public methods for interacting with data.
  2. Modularity: Break down your application into smaller, self-contained classes focused on a single responsibility.
  3. Reusability: Extend and reuse functionality through inheritance and polymorphism.

Documentation and Tools

Refer to the official Python documentation on classes for more detailed information.

Related Posts