Textualize/textual · error · AttributeError

Can't set {obj}.{self.name!r}; reactive attributes with a co

Error message

Can't set {obj}.{self.name!r}; reactive attributes with a compute method are read-only

What it means

Textual reactives backed by a compute_ method are derived values and are read-only. Assigning to such a reactive raises AttributeError.

Source

Thrown at src/textual/reactive.py:331

        else:
            return getattr(obj, internal_name)

    def _set(self, obj: Reactable, value: ReactiveType, always: bool = False) -> None:
        _rich_traceback_omit = True

        if not hasattr(obj, "_id"):
            raise ReactiveError(
                f"Node is missing data; Check you are calling super().__init__(...) in the {obj.__class__.__name__}() constructor, before setting reactives."
            )

        if isinstance(value, _Mutated):
            value = value.value
            always = True

        self._initialize_reactive(obj, self.name)

        if hasattr(obj, self.compute_name):
            raise AttributeError(
                f"Can't set {obj}.{self.name!r}; reactive attributes with a compute method are read-only"
            )

        name = self.name
        current_value = getattr(obj, name)
        # Check for private and public validate functions.
        private_validate_function = getattr(obj, f"_validate_{name}", None)
        if callable(private_validate_function):
            value = private_validate_function(value)
        public_validate_function = getattr(obj, f"validate_{name}", None)
        if callable(public_validate_function):
            value = public_validate_function(value)

        # Toggle the classes using the value's truthiness
        if (toggle_class := self._toggle_class) is not None:
            obj.set_class(bool(value), *toggle_class.split())

        # If the value has changed, or this is the first time setting the value

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Remove the assignment; change the inputs the compute method depends on instead
  2. If the reactive should be writable, delete the compute_ method and rely on watch_/validate_ hooks
  3. Use set_reactive() only if you truly need to bypass watchers, understanding computed values are still re-derived

Example fix

# before
class Sidebar(Widget):
    width_ratio = reactive(0.3)
    def compute_width_ratio(self): return self.expanded and 0.5 or 0.3
sidebar.width_ratio = 0.5  # raises
# after
class Sidebar(Widget):
    expanded = reactive(False)
    width_ratio = reactive(0.3)
    def compute_width_ratio(self): return 0.5 if self.expanded else 0.3
sidebar.expanded = True
Defensive patterns

Strategy: type-guard

Validate before calling

from textual.reactive import Reactive
if hasattr(owner, f"compute_{name}"):
    raise AttributeError(f"{name} is computed and read-only")

Type guard

def is_writable_reactive(obj: object, name: str) -> bool:
    return not hasattr(obj, f"compute_{name}")

Try / catch

try:
    widget.foo = value
except AttributeError as e:
    if "compute method" in str(e):
        change_foo_inputs_instead()

Prevention

When it happens

Trigger: A class defines both a reactive 'foo' and a 'watch_foo'/compute method chain where 'foo' has a compute method (compute_foo), and code assigns widget.foo = value.

Common situations: Trying to override or reset a computed reactive (like a computed layout property) from an event handler or test setup.

Related errors


AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27). Data as JSON: /api/errors/3dbdbd25cb4623f3. Report an issue: GitHub.