Encapsulation and Properties
Python has no true “private” access modifiers like Java’s private. Instead it relies on naming conventions and the @property decorator.
Naming Conventions
class Account:
def __init__(self, balance):
self.balance = balance # public: no restriction, freely accessible
self._pin = "1234" # single underscore: "internal use", a CONVENTION only
self.__secret_key = "xyz" # double underscore: name-mangled (see below)Single underscore is a convention, not enforcement
self._pinis still fully accessible from outside (account._pinworks fine). It is a signal to other developers: “treat this as internal, do not rely on it externally.”
Name Mangling (Double Leading Underscore)
class Account:
def __init__(self):
self.__secret = "hidden"
a = Account()
a.__secret # AttributeError!
a._Account__secret # 'hidden' -- name mangling renamed it internally to _ClassName__attrPurpose of name mangling
This exists mainly to avoid naming collisions in inheritance hierarchies, not to provide real privacy. It is still trivially bypassable, so do not treat it as a security boundary.
@property: Computed Attributes with Getter Syntax
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14159 * self.radius ** 2 # computed on the fly, accessed like an attribute
c = Circle(5)
c.area # 78.53975 -- called WITHOUT parentheses, like a plain attribute@property with a Setter: Validation on Assignment
class Circle:
def __init__(self, radius):
self.radius = radius # goes through the setter below via the property
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("Radius must be positive")
self._radius = value
c = Circle(5)
c.radius = 10 # goes through validation
c.radius = -3 # raises ValueErrorWhy this pattern matters
Callers still use plain attribute syntax (
c.radius = 10), no getter/setter method calls needed like in Java (c.setRadius(10)). This lets you START with a plain public attribute, and LATER add validation via@propertywithout breaking any existing code that usesobj.attrsyntax.
Read-Only Properties
Omit the setter to make an attribute read-only from outside the class.
class Employee:
def __init__(self, first, last):
self._first = first
self._last = last
@property
def full_name(self):
return f"{self._first} {self._last}"
e = Employee("Bob", "Smith")
e.full_name # 'Bob Smith'
e.full_name = "X" # AttributeError: can't set attribute (no setter defined)@<property>.deleter
class Circle:
@property
def radius(self):
return self._radius
@radius.deleter
def radius(self):
print("Deleting radius")
del self._radius
c = Circle()
c._radius = 5
del c.radius # prints 'Deleting radius'Encapsulation Philosophy in Python
"We are all consenting adults here"
This is a well-known Python community phrase. Rather than strictly enforcing privacy, Python trusts developers to respect naming conventions. This makes introspection and debugging much easier (you can always poke at
_internalattributes when needed) at the cost of relying on discipline rather than compiler enforcement.