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 valueView on GitHub (pinned to 06dbeef4bb)
Solutions
- Remove the assignment; change the inputs the compute method depends on instead
- If the reactive should be writable, delete the compute_ method and rely on watch_/validate_ hooks
- 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
- Name compute methods compute_<attr> deliberately
- Document computed reactives as derived state
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
- Widget.children is read-only: use Widget.mount(...) or Widge
- A Reactive class is required; for example: MyApp.theme
- Unable to bind non-reactive attribute {name!r} on {self}
- Unable to bind data; {reactive.owner.__name__} is not define
- Node is missing data; Check you are calling super().__init__
AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27).
Data as JSON: /api/errors/3dbdbd25cb4623f3.
Report an issue: GitHub.