The @property decorator allows you to turn a method into a "virtual" attribute of a class: it appears to the user of the class as a regular property but is controlled by the logic of a Python function. Initially, in Python, all instance attributes were directly accessible, but to support encapsulation, a mechanism was needed to manage access to data without changing the class interface.
History:
In early versions of Python, there was no explicit mechanism for restricting access to attributes. The task of encapsulation was achieved through conventions (for example, underlining), but any changes to the storage/validation logic for values would break compatibility. With the introduction of the @property decorator, it became possible to declare a method as an attribute, provide it with a getter, setter, and deleter, while keeping the previous class interface.
Problem:
When there is a need to subsequently add validation logic or compute a field's value, reconstructing the entire interface is too costly — it requires modifying all access points to the variable. At the same time, the visibility of the internal implementation of the field should not be exposed to the external consumer of the class. @property allows for easy "virtualization" of access.
Solution:
@property implements the "getter/setter with interface protection" pattern: you can add a computed property or control the assignment of a value without changing the client code.
Code example:
class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("Temperature below absolute zero!") self._celsius = value @property def fahrenheit(self): return self._celsius * 9 / 5 + 32 # Usage obj = Temperature(25) print(obj.celsius) # 25 print(obj.fahrenheit) # 77.0 obj.celsius = -300 # ValueError
Key features:
Is it always possible to add a property to any class field without breaking backward compatibility?
No. If the attribute was public and used as a regular variable, replacing it with a property can be done transparently. But if somewhere the accesses went through __dict__ or type checks were performed via type(obj.attr), unexpected behavior may occur.
Can only a setter be declared without a getter for a property?
No, the getter (the method with @property) is obligatory at first; otherwise, Python does not know which property to "link" the setter to.
What is the order of decorators @property/@setter/@deleter and why?
@property is always written first (it creates a property object), followed by the method name — @<name>.setter and @<name>.deleter (they complement the previously created property).
class A: @property def value(self): ... @value.setter def value(self, v): ...
Class fields were public, then a computed property was added via property after the public API was released. In the old code, accessing the attribute implicitly changes behavior or causes an error.
Pros:
Cons:
__dict__Fields were defined as private (with one underscore) right from the design stage, and users worked only through methods/properties. Adding new logic to the property in the future occurred without changes to the client interface.
Pros:
Cons: