-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: avoid name conflicts (option 2) use __get__ directly to create signal instances #262
Conversation
CodSpeed Performance ReportMerging #262 will improve performances by 10.55%Comparing Summary
Benchmarks breakdown
|
Follow up on #260 (comment) Positive:
Negative
# Rough implementation, I have to figure out the exact layout because of the descriptors
class SignalAggInstance(SignalInstance):
def _slot_relay(self, *args: Any) -> None:
pass
...
class SignalAgg(Signal):
_signal_instance_class: SignalAggInstance
# Container for Signal descriptors
class SignalList:
pass
class SignalContainer:
_agg_: ClassVar[SignalAgg]
_signals_: ClassVar[SignalList]
_uniform_: ClassVar[bool] = False
_signal_aliases_: ClassVar[Mapping[str, str | None]]
def __init_subclass__(
cls,
strict: bool = False,
signal_aliases: Mapping[str, str] = {},
) -> None:
"""Finds all Signal instances on the class and add them to `cls._signals_`."""
all_signals = {}
for k in dir(cls):
v = getattr(cls, k)
if isinstance(v, Signal):
all_signals[k] = v
# delete from dir
delattr(cls, k)
cls._signals_ = type(f"{cls.__name__}List", (SignalList, ), all_signals)
cls._uniform_ = _is_uniform(cls._signals_.values())
if strict and not cls._uniform_:
raise TypeError(
"All Signals in a strict SignalGroup must have the same signature"
)
cls._signal_aliases_ = {**signal_aliases}
# Create aggregated signal
cls._agg_ = Signal(...)
return super().__init_subclass__()
def __init__(self):
self.__agg__attribute_name__ == "agg"
for k, v in self._signal_aliases_.items():
if v == "__agg__":
self.__agg__attribute_name__ == k
break
def get_aggregated_signals(self):
return self._agg_
def get_signal(self, name: str):
return getattr(self._signals_, name, None)
def __getattr__(self, name: str, owner):
sig = self.get_signal(name)
if isinstance(sig, SignalInstance):
return sig
if name == self.__agg__attribute_name__:
return self.get_aggregated_signals()
raise AttributeError
class Events(SignalContainer):
sig1 = Signal(str)
sig2 = Signal(str)
events = Events()
def some_callback(record):
record.signal # the SignalInstance that emitted
record.args # the args that were emitted
events.agg.connect(some_callback)
events.sig1.connect(print)
events.sig2.connect(print) |
i can definitely see the merit of deprecating/breaking the pattern of using the Given the size of the change, I will want to ping some interested parties. I think napari is moving towards fully swapping out their events with psygnal, and we'll want to involve some of them as well. So, consider me enthused and intrigued, but will need more time to go through these proposals, summarize them, and get opinions. Thanks again for you all your time! |
One thing that could be done as a quick fix also is defining the class Signal:
...
def __set__(self, instance: Any, value: Any) -> None:
"""Define __set__ to make it a data descriptor.
A data descriptor takes precedence over normal attributes with the same name.
"""
raise AttributeError(
f"Setting SignalInstance {self._name!r} to a new value is not allowed.\n"
f"Maybe the signal name conflicts with an attribute of {type(instance)}"
)
... |
closing this in favor of #269 |
alternative to #260, for discussion