Skip to content
On this page

Access Restriction

In a class, attributes and methods can be defined, allowing external code to manipulate data through instance variable methods, thereby hiding complex internal logic.

However, in the previous definition of the Student class, external code can freely modify an instance's name and score attributes:

python
>>> bart = Student('Bart Simpson', 59)
>>> bart.score
59
>>> bart.score = 99
>>> bart.score
99

To prevent external access to internal attributes, prefix the attribute name with two underscores __. In Python, instance variable names that start with __ become private, accessible only internally. Therefore, we modify the Student class:

python
class Student(object):
    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

After this change, external code cannot access instance variables .__name and .__score:

python
>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

This ensures that external code cannot arbitrarily modify the object's internal state, making the code more robust through access restriction.

What if external code needs to access name and score? We can add get_name and get_score methods to the Student class:

python
class Student(object):
    ...

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score

If we want to allow external code to modify score, we can add a set_score method:

python
class Student(object):
    ...

    def set_score(self, score):
        self.__score = score

You might wonder why we don't just use bart.score = 99 directly; we define a method to validate the parameter, preventing invalid input:

python
class Student(object):
    ...

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            raise ValueError('bad score')

Note that in Python, variable names like __xxx__, which begin and end with double underscores, are special variables and can be accessed directly; they are not private. Therefore, avoid using names like __name__ or __score__.

Sometimes you may see instance variable names starting with a single underscore, like _name. Such variables are accessible from outside, but conventionally, it signals that they should be treated as private and not accessed directly.

Are double-underscore variables completely inaccessible from outside? Not exactly. You cannot directly access __name because the Python interpreter changes it to _Student__name, allowing access like this:

python
>>> bart._Student__name
'Bart Simpson'

However, it's strongly advised against doing this, as different Python interpreter versions may change the variable name differently.

In summary, Python itself does not have mechanisms to prevent misuse; it's all about self-discipline.

Finally, be aware of this incorrect usage:

python
>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name'  # Setting __name variable!
>>> bart.__name
'New Name'

At first glance, it seems external code "successfully" set the __name variable, but this __name is not the same as the internal one! The internal __name has been automatically changed by the Python interpreter to _Student__name, while external code added a new __name variable. Try it:

python
>>> bart.get_name()  # get_name() returns self.__name
'Bart Simpson'

Exercise

Please hide the gender field of the Student object from external access, using get_gender() and set_gender() methods, and validate the parameters:

python
class Student(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

# Test:
bart = Student('Bart', 'male')
if bart.get_gender() != 'male':
    print('Test failed!')
else:
    bart.set_gender('female')
    if bart.get_gender() != 'female':
        print('Test failed!')
    else:
        print('Test succeeded!')
Access Restriction has loaded