Private attributes
The code that creates objects of a class and uses them is called the class's client. A well-made class keeps its objects in a sensible state whatever the client does: a thermostat should never be set to 45 degrees, and a bank balance shouldn't change except by deposits and withdrawals. Keeping the attributes of an object acceptable at all times is called keeping its integrity.
A method can check a value before storing it. But as long as the attribute itself is in reach, the client can skip the method and change the attribute directly:
class Thermostat:
def __init__(self):
self.target = 20
def set_target(self, degrees):
if 5 <= degrees <= 30:
self.target = degrees
heater = Thermostat()
heater.set_target(45)
print(heater.target)
heater.target = 45
print(heater.target)
20
45The method refused 45, and then the client set it anyway.
Two underscores make it private
An attribute whose name starts with two underscores, such as __target, is private: only the class's own methods can use it by that name. Code outside the class that tries to read it gets an error. To let the client see the value, the class offers a method that returns it:
class Thermostat:
def __init__(self):
self.__target = 20
def set_target(self, degrees):
if 5 <= degrees <= 30:
self.__target = degrees
def get_target(self):
return self.__target
heater = Thermostat()
heater.set_target(22)
print(heater.get_target())
print(heater.__target)
22Traceback (most recent call last):
File "main.py", line 16, in <module>
print(heater.__target)
^^^^^^^^^^^^^^^
AttributeError: 'Thermostat' object has no attribute '__target'. Did you mean: 'get_target'?Inside the class, self.__target works as usual. Outside it, the attribute can't be reached by that name (Python even suggests the public method to use instead), so the only way to change the target is set_target, which checks the value.
Hiding an object's attributes and offering methods to use them is called encapsulation. The client tells the object what to do, and the object's own methods decide how. That also leaves the class free to change: if Thermostat later kept its target in tenths of a degree, only its methods would need rewriting, and every program that calls set_target and get_target would go on working.
Private, not secret
One thing outside code can do without an error is assign to the private name. But that doesn't reach the private attribute:
class Thermostat:
def __init__(self):
self.__target = 20
def get_target(self):
return self.__target
heater = Thermostat()
heater.__target = 45
print(heater.__target)
print(heater.get_target())
print(heater._Thermostat__target)
45
20
20heater.__target = 45 gave the object a new, unrelated attribute that happens to be called __target, and the thermostat's real setting stayed at 20. The last line shows why: inside a class, Python quietly renames every private name, putting an underscore and the class's name in front. So the real attribute is _Thermostat__target, and only code inside the class uses it by its short name.
That renaming is all "private" means in Python. Anyone who writes out the long name can still reach the attribute, so the two underscores aren't a lock but a label: this belongs to the class, use its methods. Treat it that way, and never write a name like _Thermostat__target in your own programs.
The two underscores go only at the start of the name. A name with two underscores at both ends, like __init__ or __str__, is one of Python's special methods, and it isn't private.