psygnal0.11.1
Published
Fast python callback/event system modeled after Qt Signals
pip install psygnal
Package Downloads
Authors
Project URLs
Requires Python
>=3.8
Dependencies
- ipython
; extra == "dev"
- mypy
; extra == "dev"
- mypy-extensions
; extra == "dev"
- pre-commit
; extra == "dev"
- pyqt5
; extra == "dev"
- pytest-mypy-plugins
; extra == "dev"
- rich
; extra == "dev"
- ruff
; extra == "dev"
- typing-extensions
; extra == "dev"
- griffe
==0.25.5; extra == "docs"
- mkdocs-material
==8.5.10; extra == "docs"
- mkdocs-minify-plugin
; extra == "docs"
- mkdocs-spellcheck
[all]; extra == "docs"
- mkdocs
==1.4.2; extra == "docs"
- mkdocstrings-python
==0.8.3; extra == "docs"
- mkdocstrings
==0.20.0; extra == "docs"
- wrapt
; extra == "proxy"
- pydantic
; extra == "pydantic"
- attrs
; extra == "test"
- dask
; extra == "test"
- msgspec
; extra == "test"
- numpy
; extra == "test"
- pydantic
; extra == "test"
- pyinstaller
>=4.0; extra == "test"
- pytest-cov
; extra == "test"
- pytest
>=6.0; extra == "test"
- toolz
; extra == "test"
- wrapt
; extra == "test"
- pytest-qt
; extra == "testqt"
- qtpy
; extra == "testqt"
psygnal
Psygnal (pronounced "signal") is a pure python implementation of the observer pattern, with the API of Qt-style Signals with (optional) signature and type checking, and support for threading. It has no dependencies.
This library does not require or use Qt in any way, It simply implements a similar observer pattern API.
Documentation
https://psygnal.readthedocs.io/
Install
pip install psygnal
conda install -c conda-forge psygnal
Usage
The observer pattern is a software design pattern in which an object maintains a list of its dependents ("observers"), and notifies them of any state changes – usually by calling a callback function provided by the observer.
Here is a simple example of using psygnal:
from psygnal import Signal
class MyObject:
# define one or more signals as class attributes
value_changed = Signal(str)
# create an instance
my_obj = MyObject()
# You (or others) can connect callbacks to your signals
@my_obj.value_changed.connect
def on_change(new_value: str):
print(f"The value changed to {new_value}!")
# The object may now emit signals when appropriate,
# (for example in a setter method)
my_obj.value_changed.emit('hi') # prints "The value changed to hi!"
Much more detail available in the documentation!
Evented Dataclasses
A particularly nice usage of the signal pattern is to emit signals whenever a
field of a dataclass changes. Psygnal provides an @evented
decorator that will
emit a signal whenever a field changes. It is compatible with dataclasses
from the standard library,
as well as attrs, and
pydantic:
from psygnal import evented
from dataclasses import dataclass
@evented
@dataclass
class Person:
name: str
age: int = 0
person = Person('John', age=30)
# connect callbacks
@person.events.age.connect
def _on_age_change(new_age: str):
print(f"Age changed to {new_age}")
person.age = 31 # prints: Age changed to 31
See the dataclass documentation for more details.
Evented Containers
psygnal.containers
provides evented versions of mutable data structures
(dict
, list
, set
), for cases when you need to monitor mutation:
from psygnal.containers import EventedList
my_list = EventedList([1, 2, 3, 4, 5])
my_list.events.inserted.connect(lambda i, val: print(f"Inserted {val} at index {i}"))
my_list.events.removed.connect(lambda i, val: print(f"Removed {val} at index {i}"))
my_list.append(6) # Output: Inserted 6 at index 5
my_list.pop() # Output: Removed 6 at index 5
See the evented containers documentation for more details.
Benchmark history
https://pyapp-kit.github.io/psygnal/
and
https://codspeed.io/pyapp-kit/psygnal
Developers
Compiling
While psygnal
is a pure python package, it is compiled with mypyc to increase
performance. To test the compiled version locally, you can run:
make build
(which is just an alias for HATCH_BUILD_HOOKS_ENABLE=1 pip install -e .
)
Debugging
To disable all compiled files and run the pure python version, you may run:
python -c "import psygnal.utils; psygnal.utils.decompile()"
To return the compiled version, run:
python -c "import psygnal.utils; psygnal.utils.recompile()"
The psygnal._compiled
variable will tell you if you're using the compiled
version or not.