Main Ecosystems
HomePython CorePandas ReferenceNumPy ScientificFastAPI & PydanticDjango Enterprise
More Ecosystems
Environment & SetupRequests & HTTPAsyncIO ConcurrencyObject-Oriented OOPPyTorch Deep LearningScikit-Learn MLFlask FrameworkWeb ScrapingDatabase & ORMDevOps & Docker

RecursionError in Python @property Getter and Setter

Verified FixPython 3.10+Python Native OOPSilo: oop

Quick Fix / Solution Rapide

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)

Example: Bug Reproduction
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.

Example: Recommended Solution
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__.

Example: Alternative Solution
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.