In many languages, you can mark fields as
private so other code cannot touch them. Python takes a different approach: it trusts that developers will behave.The single underscore convention
class BankAccount:
def __init__(self, owner):
self.owner = owner
self._balance = 0.0
acc = BankAccount('Ada')
print(acc._balance) # prints 0.0_balance signals 'internal use only'Prefixing an attribute with a single underscore (like
_balance) is a convention. It tells other programmers: "This is for the class's internal machinery. Don't rely on it or change it directly." Python does not actually prevent access, though.Using @property to control reads
class Player:
def __init__(self):
self._health = 100
@property
def health(self):
return self._health
p = Player()
print(p.health) # prints 100
# p.health = 50 would raise AttributeError@property makes health look like an attribute but run codeThe
@property decorator lets you define a method that behaves like a read-only attribute. When someone writes p.health, Python calls your function and returns the result, instead of letting them poke at _health directly.Adding validation with setters
class Player:
def __init__(self):
self._level = 1
@property
def level(self):
return self._level
@level.setter
def level(self, value):
if not isinstance(value, int) or value < 1:
raise ValueError("Level must be a positive integer")
self._level = value
p = Player()
p.level = 5 # works fine
# p.level = -2 would raise an error@name.setter lets you validate before storingBy attaching a setter to the same property name, you can check incoming values. If they are invalid, you raise an exception and never store bad data in
_level. This keeps your object's state consistent.