replace weaksets in reactive

This commit is contained in:
Will McGugan
2022-11-03 10:49:12 +00:00
parent d528e00915
commit a9352cd0ba
2 changed files with 19 additions and 9 deletions

View File

@@ -284,6 +284,7 @@ class MessagePump(metaclass=MessagePumpMeta):
await timer.stop() await timer.stop()
self._timers.clear() self._timers.clear()
await self._message_queue.put(events.Unmount(sender=self)) await self._message_queue.put(events.Unmount(sender=self))
Reactive._reset_object(self)
await self._message_queue.put(None) await self._message_queue.put(None)
if self._task is not None and asyncio.current_task() != self._task: if self._task is not None and asyncio.current_task() != self._task:
# Ensure everything is closed before returning # Ensure everything is closed before returning

View File

@@ -3,7 +3,6 @@ from __future__ import annotations
from functools import partial from functools import partial
from inspect import isawaitable from inspect import isawaitable
from typing import TYPE_CHECKING, Any, Callable, Generic, Type, TypeVar, Union from typing import TYPE_CHECKING, Any, Callable, Generic, Type, TypeVar, Union
from weakref import WeakSet
from . import events from . import events
from ._callback import count_parameters, invoke from ._callback import count_parameters, invoke
@@ -102,6 +101,7 @@ class Reactive(Generic[ReactiveType]):
Args: Args:
obj (Reactable): An object with Reactive descriptors obj (Reactable): An object with Reactive descriptors
""" """
if not hasattr(obj, "__reactive_initialized"): if not hasattr(obj, "__reactive_initialized"):
startswith = str.startswith startswith = str.startswith
for key in obj.__class__.__dict__: for key in obj.__class__.__dict__:
@@ -116,6 +116,16 @@ class Reactive(Generic[ReactiveType]):
setattr(obj, name, default_value) setattr(obj, name, default_value)
setattr(obj, "__reactive_initialized", True) setattr(obj, "__reactive_initialized", True)
@classmethod
def _reset_object(cls, obj: object) -> None:
"""Reset reactive structures on object (to avoid reference cycles).
Args:
obj (object): A reactive object.
"""
getattr(obj, "__watchers", {}).clear()
getattr(obj, "__computes", []).clear()
def __set_name__(self, owner: Type[MessageTarget], name: str) -> None: def __set_name__(self, owner: Type[MessageTarget], name: str) -> None:
# Check for compute method # Check for compute method
@@ -224,8 +234,7 @@ class Reactive(Generic[ReactiveType]):
) )
# Check for watchers set via `watch` # Check for watchers set via `watch`
watcher_name = f"__{name}_watchers" watchers: list[Callable] = getattr(obj, "__watchers", {}).get(name, [])
watchers = getattr(obj, watcher_name, ())
for watcher in watchers: for watcher in watchers:
obj.post_message_no_wait( obj.post_message_no_wait(
events.Callback( events.Callback(
@@ -312,11 +321,11 @@ def watch(
callback (Callable[[Any], object]): A callable to call when the attribute changes. callback (Callable[[Any], object]): A callable to call when the attribute changes.
init (bool, optional): True to call watcher initialization. Defaults to True. init (bool, optional): True to call watcher initialization. Defaults to True.
""" """
watcher_name = f"__{attribute_name}_watchers"
current_value = getattr(obj, attribute_name, None) if not hasattr(obj, "__watchers"):
if not hasattr(obj, watcher_name): setattr(obj, "__watchers", {})
setattr(obj, watcher_name, WeakSet()) watchers: dict[str, list[Callable]] = getattr(obj, "__watchers")
watchers = getattr(obj, watcher_name) watchers.setdefault(attribute_name, []).append(callback)
watchers.add(callback)
if init: if init:
current_value = getattr(obj, attribute_name, None)
Reactive._check_watchers(obj, attribute_name, current_value) Reactive._check_watchers(obj, attribute_name, current_value)