Of course. The property built-in in Python 2.7 is a powerful and elegant way to create managed attributes. It allows you to replace a simple attribute access (like obj.x) with a method call (like obj.get_x()) without changing the public interface of your class. This is often called "data hiding" or "encapsulation."

Let's break down how it works in Python 2.7.
The Core Problem: Why Do We Need property?
Imagine you have a class that represents a temperature in Celsius.
# BEFORE: The "naive" way
class Celsius:
def __init__(self, temperature):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
# This works fine...
t = Celsius(25)
print t.temperature # Output: 25
print t.to_fahrenheit() # Output: 77.0
# But what if you want to add validation?
# You can't just do this, because it would break existing code:
# if temperature < -273.15:
# raise ValueError("Temperature cannot be below absolute zero!")
You have two bad choices:
- Add validation in
__init__: This only works for initial creation. What if someone changes the attribute later?t.temperature = -300would be allowed. - Use getter/setter methods: You change the public API. Instead of
t.temperature, you force users to callt.get_temperature()andt.set_temperature(30). This is cumbersome and breaks existing code that used the attribute directly.
property solves this perfectly. It lets you keep the simple obj.attribute syntax while running custom code (like validation) behind the scenes.

How property Works in Python 2.7
The property() function can be called in two main ways:
The Simple Way: property(fget, fset, fdel, doc)
You pass it four optional arguments:
fget: A function to get the attribute (the "getter").fset: A function to set the attribute (the "setter").fdel: A function to delete the attribute (the "deleter").doc: A docstring for the property.
Let's refactor our Celsius class using this method.
class Celsius:
def __init__(self, temperature):
# We call the setter here to ensure validation on initialization
self.temperature = temperature
def get_temperature(self):
"""This is the getter."""
print("Getting value...")
return self._temperature
def set_temperature(self, value):
"""This is the setter."""
print("Setting value...")
if value < -273.15:
raise ValueError("Temperature cannot be below absolute zero!")
self._temperature = value
def del_temperature(self):
"""This is the deleter."""
print("Deleting value...")
del self._temperature
# This is the magic!
# We create a property object named 'temperature'
temperature = property(get_temperature, set_temperature, del_temperature,
"A temperature in Celsius.")
# --- Usage ---
# 1. Setting a value (calls the setter)
t = Celsius(37.5)
# Output: Setting value...
# 2. Getting a value (calls the getter)
print t.temperature
# Output:
# Getting value...
# 37.5
# 3. Deleting the attribute (calls the deleter)
del t.temperature
# Output: Deleting value...
# 4. Accessing the docstring
print Celsius.temperature.__doc__
# Output: A temperature in Celsius.
# 5. Validation is enforced
try:
t = Celsius(-300)
except ValueError as e:
print e
# Output: Setting value!
# Temperature cannot be below absolute zero!
Key Points:

- We use a name like
_temperature(a single leading underscore) for the "backing" attribute. This is a Python convention for "internal use" variables. - When you write
t.temperature = 25, Python calls theset_temperaturemethod. - When you write
print t.temperature, Python calls theget_temperaturemethod. - The public interface remains clean:
t.temperature, nott.get_temperature().
The "Decorator" Way (More Pythonic and Common)
This syntax was introduced in Python 2.4 and is much cleaner. It uses the @property and @{attribute_name}.setter decorators.
The logic is:
@property: Decorates the getter method. This method defines the property and must return the value.@{property_name}.setter: Decorates the setter method. This method takes the value being assigned as an argument.
Let's rewrite the Celsius class using decorators.
class Celsius:
def __init__(self, temperature):
# The setter is called during __init__
self.temperature = temperature
@property
def temperature(self):
"""This is the getter. It's a property now."""
print("Getting value...")
return self._temperature
@temperature.setter
def temperature(self, value):
"""This is the setter. It's linked to the 'temperature' property."""
print("Setting value...")
if value < -273.15:
raise ValueError("Temperature cannot be below absolute zero!")
self._temperature = value
# You can also add a deleter if you want
@temperature.deleter
def temperature(self):
"""This is the deleter."""
print("Deleting value...")
del self._temperature
# --- Usage is EXACTLY the same as before ---
print("--- Using Decorator Syntax ---")
t = Celsius(40)
# Output: Setting value...
print t.temperature
# Output:
# Getting value...
# 40
t.temperature = 42
# Output: Setting value!
print t.temperature
# Output:
# Getting value...
# 42
del t.temperature
# Output: Deleting value!
This is the preferred method in modern Python (and it works the same in Python 3). It's more readable because the getter, setter, and deleter are all defined with the same name, clearly grouping them together.
Common Use Cases for property
- Validation: As shown above, ensuring a value is within a certain range.
- Lazy Computation: Calculating an attribute's value only the first time it's requested and then caching it.
- Logging/Debugging: Logging every time an attribute is accessed, modified, or deleted.
- Interface Consistency: Exposing a complex internal calculation as a simple attribute. For example, a
Personclass could have afull_nameproperty that returnsself.first_name + " " + self.last_name.
Example: Lazy Computation
class Circle:
def __init__(self, radius):
self.radius = radius
self._area = None # Cached value
@property
def area(self):
"""Calculates area on first access, then caches it."""
if self._area is None:
print("Calculating area...")
self._area = 3.14159 * (self.radius ** 2)
return self._area
c = Circle(10)
print c.area
# Output:
# Calculating area...
# 314.159
print c.area
# Output:
# 314.159 # No calculation this time, just returns the cached value
Summary: property in Python 2.7
| Feature | Description |
|---|---|
| Purpose | To create managed attributes that look like simple data attributes but have custom logic (get, set, delete). |
| Syntax | Two main ways: the property(fget, fset, fdel) function and the @property decorator syntax. |
| Decorator Syntax (Preferred) | @property for the getter, @{name}.setter for the setter. This is cleaner and more common. |
| Use Cases | Validation, lazy loading, logging, and providing a clean interface for complex logic. |
| Python 2 vs 3 | The functionality is identical. The decorator syntax works in both. The only difference is that in Python 3, you can also use @{name}.deleter. |
