reflex-dev/reflex · error · TypeError

Cannot assign the same cached_property to two different name

Error message

Cannot assign the same cached_property to two different names ({self._attrname!r} and {name!r}).

What it means

This is CPython's cached_property guard reimplemented in Reflex: a single cached_property descriptor instance was assigned to two different attributes of the same class. Python's cached_property stores its attribute name in _attrname when __set_name__ runs, and refuses to be reused under a second name. Reflex's copy preserves that invariants check.

Source

Thrown at packages/reflex-base/src/reflex_base/vars/base.py:2051

                try:
                    unique_id = object.__getattribute__(this, cached_field_name)
                except AttributeError:
                    if original_del is not None:
                        original_del(this)
                    return
                GLOBAL_CACHE.pop(unique_id, None)

                if original_del is not None:
                    original_del(this)

            owner.__del__ = delete_property

        elif name != self._attrname:
            msg = (
                "Cannot assign the same cached_property to two different names "
                f"({self._attrname!r} and {name!r})."
            )
            raise TypeError(msg)

    def __get__(self, instance: Any, owner: type | None = None):
        """Get the cached property.

        Args:
            instance: The instance to get the cached property from.
            owner: The owner of the cached property.

        Returns:
            The cached property.

        Raises:
            TypeError: If the class does not have __set_name__.
        """
        if self._attrname is None:
            msg = "Cannot use cached_property on a class without __set_name__."
            raise TypeError(msg)
        cached_field_name = "_reflex_cache_" + self._attrname

View on GitHub (pinned to 45b8ed5ab7)

Solutions

  1. Create a fresh property for each attribute (call the decorator/factory once per name, or copy.copy the descriptor before reassigning)
  2. If aliasing is intentional, use functools.cached_property semantics correctly: re-run the decorator for the second name instead of assigning the same object
  3. Find the duplicate assignment with grep for the attribute/property name in class bodies and metaprogramming code

Example fix

# before
make_prop = _cached_property(some_fget)
class MyState(rx.State):
    a = make_prop
    b = make_prop  # TypeError

# after
class MyState(rx.State):
    a = _cached_property(some_fget)
    b = _cached_property(other_fget)  # or the same callable, new descriptor

# or alias the result, not the descriptor:
#   b = property(lambda self: self.a)
Defensive patterns

Strategy: validation

Validate before calling

def validate_cached_props(cls) -> None:
    seen = {}
    for name in vars(cls):
        obj = vars(cls)[name]
        attrname = getattr(obj, '_attrname', None)
        if attrname is not None and obj is not getattr(cls, attrname, obj):
            pass
        if id(obj) in seen:
            raise TypeError(f'{type(obj).__name__} assigned to both {seen[id(obj)]!r} and {name!r}')
        seen[id(obj)] = name

Type guard

def is_fresh_descriptor(obj, assigned_names: set[str]) -> bool:
    # a cached_property may only be assigned to one name per class
    return id(obj) not in assigned_names

Prevention

When it happens

Trigger: Assigning the same cached_property object to two class attributes, e.g. cp = _cached_property(f); class S: a = cp; b = cp, or dynamically attaching one ComputedVar/cached property object to multiple attribute names in a State class (e.g. via setattr in a loop or metaprogramming).

Common situations: Metaprogramming over State classes that copies ComputedVar/cached_property descriptors between attributes; applying the same decorator result to multiple methods; refactoring state mixins where a property object gets aliased instead of re-created.

Related errors


AI-assisted analysis of reflex-dev/reflex@45b8ed5ab7 (2026-08-28). Data as JSON: /api/errors/b22320a98baf1041. Report an issue: GitHub.