RecursionError in Python @property Getter and Setter
Store the underlying value in a private attribute prefixed with an underscore (e.g. self._name), returning self._name in the getter instead of self.name.
Root Cause Analysis
This error occurs when a Python property getter decorated with @property attempts to return self.property_name rather than the backing private attribute self._property_name, creating an infinite recursive loop that exhausts Python's call stack.
1. The Property Lookup Mechanism in Python
When you define @property def name(self): return self.name, accessing obj.name invokes the name() method. Inside name(), evaluating self.name calls name() again. This repeats until Python hits sys.getrecursionlimit() (default 1000) and raises RecursionError: maximum recursion depth exceeded.
2. Infinite Recursion in Property Setters
Similarly, in @name.setter def name(self, value): self.name = value, assigning to self.name calls the setter itself infinitely.
3. Shadowing in init
Assigning self.name = name in __init__ when the property setter is broken triggers recursion during object instantiation.
4. Mutually Recursive Properties
Property a calling property b which in turn queries property a.
Reproduction Code (MCVE)
class Account:
def __init__(self, balance):
self.balance = balance
@property
def balance(self):
# Bug: referencing self.balance calls getter recursively
return self.balance
acc = Account(100)
print(acc.balance)
Solution 1: Use Underscore Prefix for Private Backing Attributes
Store the raw value in self._balance and access self._balance inside the property getter and setter.
class Account:
def __init__(self, balance):
self._balance = balance # Backing private variable
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError('Balance cannot be negative')
self._balance = value
acc = Account(100)
acc.balance = 250
print(f'Account balance: ${acc.balance}')
Solution 2: Use functools.cached_property for Computed Read-Only Values
Use @functools.cached_property for expensive computations that should run once and cache into __dict__.
from functools import cached_property
class DataReport:
def __init__(self, numbers):
self.numbers = numbers
@cached_property
def total(self):
print('Computing total once...')
return sum(self.numbers)
rep = DataReport([10, 20, 30])
print(rep.total)
print(rep.total) # Retrieved directly from cache
A common mistake is using double underscore mangling (self.__balance) unnecessarily. Double underscores trigger Python name mangling (_Account__balance), which complicates debugging and subclassing. A single underscore (self._balance) is the PEP 8 standard for private attributes. Edge cases occur with dataclasses: when using @property on a dataclass field, define the field with _name: str and provide name property. Contrast this error with AttributeError: can't set attribute, which occurs when trying to assign to a read-only property without a setter.