Python - Classes

13.
Discuss the use of private and protected members in a class.

In Python, members of a class can have different access levels. There are three main access levels: public, protected, and private. Public members can be accessed from anywhere, protected members are accessible within the class and its subclasses, and private members are only accessible within the class. The access level is determined by the use of underscores before the variable or method name.

Public Members:

class PublicExample:
    def __init__(self):
        self.public_variable = "This is public"

    def public_method(self):
        return "This is a public method"

# Creating an object of the class
obj = PublicExample()

# Accessing public members
print(obj.public_variable)
print(obj.public_method())

Protected Members:

class ProtectedExample:
    def __init__(self):
        # Protected variable
        self._protected_variable = "This is protected"

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

# Creating an object of the class
obj = ProtectedExample()

# Accessing protected members
print(obj._protected_variable)
print(obj._protected_method())

Private Members:

class PrivateExample:
    def __init__(self):
        # Private variable
        self.__private_variable = "This is private"

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

# Creating an object of the class
obj = PrivateExample()

# Attempting to access private members will result in an AttributeError
# print(obj.__private_variable)
# print(obj.__private_method())

In the example, PublicExample has public members, ProtectedExample has protected members, and PrivateExample has private members. Attempting to access private members outside the class will result in an AttributeError.

It's important to note that in Python, the use of underscores is a convention for indicating the access level, and it is not enforced by the language itself.

Output:

This is public
This is a public method
This is protected
This is a protected method

14.
How can you implement encapsulation in Python classes?

Encapsulation in Python is a way to restrict access to certain attributes or methods of a class, preventing them from being accessed directly from outside the class. This is achieved by marking attributes or methods as private using a double underscore (__) prefix. Private members are only accessible within the class.

Let's demonstrate encapsulation with an example:

class Student:
    def __init__(self, name, age):
        # Private attribute
        self.__name = name
        # Private attribute
        self.__age = age

    # Public method to access private attribute
    def get_name(self):
        return self.__name

    # Public method to access private attribute
    def get_age(self):
        return self.__age

    # Public method to modify private attribute
    def set_age(self, new_age):
        self.__age = new_age

# Creating an object of the class
student = Student("Alice", 20)

# Accessing private attributes using public methods
name = student.get_name()
age = student.get_age()

# Displaying initial information
print(f"Name: {name}, Age: {age}")

# Modifying private attribute using a public method
student.set_age(21)

# Accessing modified information
print(f"Name: {name}, Age: {student.get_age()}")

In this example, the Student class has private attributes (__name and __age) and public methods (get_name, get_age, and set_age) to access and modify these attributes. This encapsulation ensures that the internal state of the object is controlled through defined methods.

Output:

Name: Alice, Age: 20
Name: Alice, Age: 21

15.
Explain the purpose of the @classmethod decorator in Python.

In Python, the @classmethod decorator is used to define a method that belongs to the class rather than an instance of the class. This means the method can be called on the class itself, and it has access to the class and its attributes. The first parameter of a class method is traditionally named cls, representing the class.

Let's illustrate the purpose of the @classmethod decorator with an example:

class MyClass:
    class_variable = "I am a class variable"

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

    @classmethod
    def class_method(cls, x):
        # Accessing class variable
        return f"Class variable: {cls.class_variable}, Parameter: {x}"

# Calling the class method without creating an object
result = MyClass.class_method(42)

# Displaying the result
print(result)

In this example, the MyClass class has a class method called class_method decorated with @classmethod. This method can be called on the class itself, and it has access to the class variable (class_variable).

Output:

Class variable: I am a class variable, Parameter: 42

16.
What is the role of the @staticmethod decorator in Python classes?

In Python, the @staticmethod decorator is used to define a static method within a class. Unlike regular methods, static methods don't have access to the instance or class itself. They are defined using the @staticmethod decorator and can be called on the class without creating an instance of the class. Static methods are often used for utility functions that don't depend on the instance or class state.

Let's illustrate the role of the @staticmethod decorator with an example:

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

    @staticmethod
    def multiply(x, y):
        return x * y

# Calling static methods without creating an object
sum_result = MathOperations.add(5, 3)
product_result = MathOperations.multiply(4, 6)

# Displaying the results
print(f"Sum: {sum_result}")
print(f"Product: {product_result}")

In this example, the MathOperations class has two static methods, add and multiply, decorated with @staticmethod. These methods can be called on the class itself without creating an instance of the class.

Output:

Sum: 8
Product: 24