Inside a Class, there can be attributes and methods, and external code can manipulate data by directly calling the methods of instance variables. This hides the complex internal logic.
However, based on the previous definition of the Student class, external code can still freely modify the name and score attributes of an instance:
>>> bart = Student('Bart Simpson', 59)
>>> bart.score
59
>>> bart.score = 99
>>> bart.score
99
If we want to prevent internal attributes from being accessed externally, we can prefix the attribute names with two underscores __. In Python, if an instance variable name starts with __, it becomes a private variable that can only be accessed internally, not externally. Let’s modify the Student class accordingly:
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 modification, external code sees no visible changes, but it can no longer access the instance variables .__name and .__score from outside:
>>> 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 internal state of the object, making the code more robust through the protection of access restrictions.
But what if external code needs to retrieve name and score? We can add methods like get_name and get_score to the Student class:
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
What if we also want to allow external code to modify score? We can add a set_score method to the Student class:
class Student(object):
...
def set_score(self, score):
self.__score = score
You might ask: why go to the trouble of defining a method when we could just modify bart.score = 99 directly? The reason is that methods allow us to validate parameters and prevent invalid values from being passed:
class Student(object):
...
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
It is important to note that in Python, variable names like __xxx__ (starting and ending with double underscores) are special variables. These variables can be accessed directly and are not private variables, so you should avoid using names like __name__ or __score__.
Sometimes you will see instance variable names starting with a single underscore (e.g., _name). Such variables can be accessed externally, but by convention, seeing this naming implies: “Although I can be accessed, please treat me as a private variable and do not modify me arbitrarily.”
Is it truly impossible to access instance variables starting with double underscores from outside? Not exactly. The reason __name cannot be accessed directly is that the Python interpreter renames the __name variable to _Student__name externally. Thus, you can still access the __name variable via _Student__name:
>>> bart._Student__name
'Bart Simpson'
However, it is strongly recommended not to do this, as different versions of the Python interpreter may rename __name to different variable names.
In summary: Python itself has no mechanism to prevent you from doing “bad things” — it all relies on programmer discipline.
Finally, note the following incorrect usage:
>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # Set the __name variable!
>>> bart.__name
'New Name'
On the surface, external code has “successfully” set the __name variable, but in reality, this __name variable is not the same as the __name variable inside the class! The internal __name variable has been automatically renamed to _Student__name by the Python interpreter, while external code has simply added a new __name variable to bart. To verify this:
>>> bart.get_name() # get_name() returns self.__name internally
'Bart Simpson'
Hide the gender field of the following Student object from external access, replace it with get_gender() and set_gender(), and add parameter validation:
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!
protected_student.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def get_name(self):
return self.__name
def get_score(self):
return self.__score
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
def get_grade(self):
if self.__score >= 90:
return 'A'
elif self.__score >= 60:
return 'B'
else:
return 'C'
bart = Student('Bart Simpson', 59)
print('bart.get_name() =', bart.get_name())
bart.set_score(60)
print('bart.get_score() =', bart.get_score())
print('DO NOT use bart._Student__name:', bart._Student__name)