Skip to content

Commit

Permalink
allow to provide combiner function that could consume whole list
Browse files Browse the repository at this point in the history
  • Loading branch information
Czaki committed Feb 20, 2024
1 parent 83ccf9e commit ea98ef8
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 30 deletions.
43 changes: 22 additions & 21 deletions src/psygnal/_signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
from ._weak_callback import (
StrongFunction,
WeakCallback,
WeakMethod,
WeakSetattr,
WeakSetitem,
weak_callback,
Expand All @@ -44,7 +43,7 @@
from ._group import EmissionInfo
from ._weak_callback import RefErrorChoice

ReducerFunc = Callable[[tuple, tuple], tuple]
ReducerFunc = Union[Callable[[tuple, tuple], tuple], Callable[[list[tuple]], tuple]]

__all__ = ["Signal", "SignalInstance", "_compiled"]
_NULL = object()
Expand Down Expand Up @@ -319,7 +318,7 @@ def __init__(
) -> None:
self._name = name
self._instance: Callable = self._instance_ref(instance)
self._args_queue: list[Any] = [] # filled when paused
self._args_queue: list[tuple] = [] # filled when paused

if isinstance(signature, (list, tuple)):
signature = _build_signature(*signature)
Expand Down Expand Up @@ -933,18 +932,7 @@ def emit(
If `check_nargs` and/or `check_types` are `True`, and the corresponding
checks fail.
"""
from ._group import SignalRelay

if (
self._is_blocked
or len(self._slots) == 0
or (
len(self._slots) == 1
and isinstance(self._slots[0], WeakMethod)
and isinstance(self._slots[0].dereference().__self__, SignalRelay) # type: ignore [union-attr]
and len(self._slots[0].dereference().__self__) == 0 # type: ignore [union-attr, arg-type]
)
):
if self._is_blocked or len(self._slots) == 0:
return None

if check_nargs:
Expand Down Expand Up @@ -1087,7 +1075,7 @@ def resume(self, reducer: ReducerFunc | None = None, initial: Any = _NULL) -> No
Parameters
----------
reducer : Callable[[tuple, tuple], Any], optional
reducer : Callable[[tuple, tuple], Any], Callable[[list[tuple]], tuple], optional
If provided, all gathered args will be reduced into a single argument by
passing `reducer` to `functools.reduce`.
NOTE: args passed to `emit` are collected as tuples, so the two arguments
Expand Down Expand Up @@ -1117,10 +1105,21 @@ def resume(self, reducer: ReducerFunc | None = None, initial: Any = _NULL) -> No
if not getattr(self, "_args_queue", None):
return
if reducer is not None:
if initial is _NULL:
args = reduce(reducer, self._args_queue)
if (
hasattr(reducer, "__annotations__")
and len(reducer.__annotations__) == 2
):
args = cast(Callable[[list[tuple]], tuple], reducer)(self._args_queue)
elif initial is _NULL:
args = reduce(
cast(Callable[[tuple, tuple], tuple], reducer), self._args_queue
)
else:
args = reduce(reducer, self._args_queue, initial)
args = reduce(
cast(Callable[[tuple, tuple], tuple], reducer),
self._args_queue,
initial,
)
self._run_emit_loop(args)
else:
for args in self._args_queue:
Expand All @@ -1134,9 +1133,11 @@ def paused(
Parameters
----------
reducer : Callable[[tuple, tuple], Any], optional
If provided, all gathered args will be reduced into a single argument by
reducer : Callable[[tuple, tuple], Any] or Callable[[list[tuple]], Any], optional
If provided two arguments, all gathered args will
be reduced into a single argument by
passing `reducer` to `functools.reduce`.
If provided single argument (a list of tuples) will be passed to it
NOTE: args passed to `emit` are collected as tuples, so the two arguments
passed to `reducer` will always be tuples. `reducer` must handle that and
return an args tuple.
Expand Down
28 changes: 19 additions & 9 deletions src/psygnal/containers/_evented_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,22 +232,30 @@ def __init__(self, iterable: Iterable[_T] = ()):

def update(self, *others: Iterable[_T]) -> None:
"""Update this set with the union of this set and others."""
with self.events.items_changed.paused(_reduce_events, ((), ())):
with self.events.items_changed.paused(
_reduce_events,
):
super().update(*others)

def clear(self) -> None:
"""Remove all elements from this set."""
with self.events.items_changed.paused(_reduce_events, ((), ())):
with self.events.items_changed.paused(
_reduce_events,
):
super().clear()

def difference_update(self, *s: Iterable[_T]) -> None:
"""Remove all elements of another set from this set."""
with self.events.items_changed.paused(_reduce_events, ((), ())):
with self.events.items_changed.paused(
_reduce_events,
):
super().difference_update(*s)

def intersection_update(self, *s: Iterable[_T]) -> None:
"""Update this set with the intersection of itself and another."""
with self.events.items_changed.paused(_reduce_events, ((), ())):
with self.events.items_changed.paused(
_reduce_events,
):
super().intersection_update(*s)

def symmetric_difference_update(self, __s: Iterable[_T]) -> None:
Expand Down Expand Up @@ -299,8 +307,10 @@ def __init__(self, iterable: Iterable[_T] = ()):
super().__init__(iterable)


def _reduce_events(a: tuple, b: tuple) -> tuple[tuple, tuple]:
"""Combine two events (a and b) each of which contain (added, removed)."""
a0, a1 = a
b0, b1 = b
return (a0 + b0, a1 + b1)
def _reduce_events(li: list[tuple]) -> tuple[tuple, tuple]:
"""Combine multiple events into a single event."""
added_li, removed_li = [], []
for added, removed in li:
added_li.extend(added)
removed_li.extend(removed)
return tuple(added_li), tuple(removed_li)

0 comments on commit ea98ef8

Please sign in to comment.