Python - Access Modifiers

Hello there, aspiring Python programmers! Today, we're going to embark on an exciting journey into the world of Access Modifiers in Python. Don't worry if you're new to programming; I'll guide you through this concept step by step, with plenty of examples and explanations along the way. So, let's dive in!

Python - Access Modifiers

Access Modifiers in Python

In object-oriented programming, access modifiers are used to control the visibility and accessibility of class members (attributes and methods). While many programming languages have strict access modifiers like public, private, and protected, Python takes a more relaxed approach. It follows a philosophy often referred to as "We're all consenting adults here."

In Python, we have three types of access modifiers:

  1. Public
  2. Protected
  3. Private

Let's explore each of these with examples.

Public Members

In Python, all members are public by default. This means they can be accessed from outside the class. Here's an example:

class Student:
    def __init__(self, name, age):
        self.name = name  # Public attribute
        self.age = age    # Public attribute

    def display_info(self):  # Public method
        print(f"Name: {self.name}, Age: {self.age}")

# Creating an instance of Student
student1 = Student("Alice", 20)

# Accessing public members
print(student1.name)  # Output: Alice
student1.display_info()  # Output: Name: Alice, Age: 20

In this example, name, age, and display_info() are all public members. We can access them directly from outside the class.

Protected Members

Protected members are denoted by prefixing the member name with a single underscore (_). They are not truly private and can still be accessed from outside the class, but it's a convention to treat them as internal use only.

class Employee:
    def __init__(self, name, salary):
        self._name = name      # Protected attribute
        self._salary = salary  # Protected attribute

    def _display_salary(self):  # Protected method
        print(f"{self._name}'s salary is ${self._salary}")

# Creating an instance of Employee
emp1 = Employee("Bob", 50000)

# Accessing protected members (Note: This is possible but not recommended)
print(emp1._name)  # Output: Bob
emp1._display_salary()  # Output: Bob's salary is $50000

While we can access _name, _salary, and _display_salary(), it's generally not recommended to do so outside the class or its subclasses.

Private Members

Private members are denoted by prefixing the name with double underscores (__). Python performs name mangling for these members, making them harder (but not impossible) to access from outside the class.

class BankAccount:
    def __init__(self, account_number, balance):
        self.__account_number = account_number  # Private attribute
        self.__balance = balance                # Private attribute

    def __display_balance(self):  # Private method
        print(f"Balance: ${self.__balance}")

    def public_display(self):
        self.__display_balance()

# Creating an instance of BankAccount
account1 = BankAccount("123456", 1000)

# Trying to access private members
# print(account1.__account_number)  # This will raise an AttributeError
# account1.__display_balance()      # This will also raise an AttributeError

# Accessing private method through a public method
account1.public_display()  # Output: Balance: $1000

In this example, __account_number, __balance, and __display_balance() are private members. Attempting to access them directly from outside the class will raise an AttributeError.

Name Mangling

Remember when I mentioned that private members in Python are not truly private? This is because of a mechanism called name mangling. When you create a private member using double underscores, Python changes its name internally to make it harder to access accidentally.

Here's how it works:

class NameManglingDemo:
    def __init__(self):
        self.__private_var = "I'm private!"

demo = NameManglingDemo()
print(dir(demo))
# Output: [..., '_NameManglingDemo__private_var', ...]

# Accessing the private variable using the mangled name
print(demo._NameManglingDemo__private_var)  # Output: I'm private!

As you can see, Python renames __private_var to _NameManglingDemo__private_var. This is name mangling in action!

Python Property Object

The property() function in Python is a built-in function that creates and returns a property object. It's a way to add getter, setter, and deleter methods to class attributes.

Here's an example:

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    def get_fahrenheit(self):
        return (self._celsius * 9/5) + 32

    def set_fahrenheit(self, fahrenheit):
        self._celsius = (fahrenheit - 32) * 5/9

    fahrenheit = property(get_fahrenheit, set_fahrenheit)

# Using the property
temp = Temperature(25)
print(temp.fahrenheit)  # Output: 77.0

temp.fahrenheit = 86
print(temp._celsius)  # Output: 30.0

In this example, fahrenheit is a property that allows us to get and set the temperature in Fahrenheit while internally storing it in Celsius.

Getters and Setter Methods

Getters and setters are methods used to get and set the values of class attributes. They provide a way to access and modify private attributes while maintaining encapsulation.

Here's an example using the @property decorator, which is a more Pythonic way to implement getters and setters:

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

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise ValueError("Name must be a string")
        self._name = value

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if not isinstance(value, int) or value < 0:
            raise ValueError("Age must be a positive integer")
        self._age = value

# Using getters and setters
person = Person("Charlie", 30)
print(person.name)  # Output: Charlie

person.name = "David"
print(person.name)  # Output: David

try:
    person.age = -5
except ValueError as e:
    print(e)  # Output: Age must be a positive integer

In this example, we've created getter and setter methods for name and age. The setter methods include validation to ensure that the values being set meet certain criteria.

To summarize the methods we've discussed, here's a table in Markdown format:

Method Description Example
Public Accessible from anywhere self.name
Protected Accessible within class and subclasses (by convention) self._name
Private Name mangled to restrict access self.__name
Property Creates a property object property(get_method, set_method)
Getter Method to get the value of an attribute @property
Setter Method to set the value of an attribute @attribute.setter

And there you have it! We've covered access modifiers in Python, name mangling, property objects, and getter and setter methods. Remember, Python's approach to access control is more about convention and trust than strict rules. As you continue your Python journey, you'll find that this flexibility allows for clean and readable code while still providing ways to implement encapsulation when needed.

Keep practicing, stay curious, and happy coding!

Credits: Image by storyset