psygnal
Psygnal is a pure Python implementation of the observer pattern, providing a fast callback and event system modeled after Qt Signals & Slots. It offers optional signature and type checking for connected slots and supports threading, all without requiring or using Qt. The current version is 0.15.1, and the library is actively maintained with regular releases.
Warnings
- gotcha Cross-thread signal emission requires manual queue processing. If connecting a slot to run in a different thread (`connect(thread=...)`), the `psygnal.emit_queued()` function *must* be periodically called in the target thread's event loop to process the queued callbacks. Without this, callbacks will not be invoked across threads.
- breaking When using asynchronous callbacks (`async def` functions), the async backend (`psygnal.set_async_backend()`) must be configured *before* connecting any async callbacks. Failure to do so will result in a `RuntimeError` or `RuntimeWarning` and the callback not being called.
- gotcha By default, `psygnal` does not strictly check the number of arguments (nargs) or types of connected slots against the signal's signature. This can lead to runtime `TypeError` exceptions when the signal is emitted if the slot's signature is incompatible.
- deprecated Users migrating from the older `PySignal` library might be confused by the `Signal` class naming. `psygnal`'s primary signal class is `psygnal.Signal`, while `PySignal` used `PySignal.ClassSignal` and `PySignal.Signal` (which is similar to `psygnal.SignalInstance`). The `PySignal` library itself is deprecated and unmaintained.
Install
-
pip install psygnal
Imports
- Signal
from psygnal import Signal
- evented
from psygnal import evented
- EventedList
from psygnal.containers import EventedList
- EventedModel
from psygnal import EventedModel
- debounced
from psygnal import debounced
- throttled
from psygnal import throttled
Quickstart
from psygnal import Signal
class MyObject:
"""A simple object that emits a signal when its value changes."""
value_changed = Signal(str)
def __init__(self, initial_value: str = ""):
self._value = initial_value
def set_value(self, new_value: str):
if new_value != self._value:
self._value = new_value
self.value_changed.emit(self._value)
# Create an instance of the object
my_obj = MyObject("start")
# Connect a callback function using the .connect() method
def on_value_change_method(new_value: str):
print(f"Callback 1 (method): The value changed to '{new_value}'!")
my_obj.value_changed.connect(on_value_change_method)
# Connect another callback function using the @.connect decorator
@my_obj.value_changed.connect
def on_value_change_decorator(new_value: str):
print(f"Callback 2 (decorator): I also received: '{new_value}'!")
print("Initial value set, no emission yet.")
# Emit signals by changing the value
print("\nSetting value to 'hello':")
my_obj.set_value("hello")
print("\nSetting value to 'world':")
my_obj.set_value("world")
print("\nSetting value to 'world' again (should not emit):")
my_obj.set_value("world")
# Disconnect a callback
my_obj.value_changed.disconnect(on_value_change_method)
print("\nDisconnected 'Callback 1'. Setting value to 'psygnal':")
my_obj.set_value("psygnal")