Listener Management¶
Beyond adding listeners, Whistle provides tools to check for listeners, retrieve them, and remove them dynamically.
Checking for Listeners¶
Use has_listeners() to check if an event has registered listeners:
if dispatcher.has_listeners("user.login"):
# Dispatch only if someone is listening
dispatcher.dispatch("user.login")
This is useful for:
Performance optimization: Skip expensive event object creation
Conditional dispatch: Only dispatch when listeners are present
Feature detection: Check if a plugin or extension is active
Checking Any Listeners¶
Call has_listeners() without arguments to check if any events have listeners:
if dispatcher.has_listeners():
print("Dispatcher has listeners registered")
else:
print("No listeners registered")
This is mainly useful for debugging and testing.
Retrieving Listeners¶
Use get_listeners() to retrieve all listeners for an event:
listeners = dispatcher.get_listeners("app.start")
print(f"Number of listeners: {len(listeners)}")
The method returns a tuple of listeners in priority order.
Retrieving All Listeners¶
Call get_listeners() without arguments to get a dictionary of all events and their listeners:
all_listeners = dispatcher.get_listeners()
for event_id, listeners in all_listeners.items():
print(f"{event_id}: {len(listeners)} listeners")
This is useful for:
Debugging dispatcher state
Generating reports
Testing listener registration
Removing Listeners¶
Use remove_listener() to unregister a specific listener:
def my_listener(event):
...
# Add it
dispatcher.add_listener("event", my_listener)
# Remove it
dispatcher.remove_listener("event", my_listener)
After removal, the listener will no longer be called when the event is dispatched.
Complete Example¶
Here’s a full example demonstrating listener management:
from whistle import EventDispatcher
def main():
"""Demonstrate listener management operations."""
dispatcher = EventDispatcher()
def listener_one(event):
print("Listener one executed")
def listener_two(event):
print("Listener two executed")
# Check if event has listeners before registering
print("=== Initial State ===")
print(f"Has listeners for 'app.start': {dispatcher.has_listeners('app.start')}")
# Register listeners
dispatcher.add_listener("app.start", listener_one)
dispatcher.add_listener("app.start", listener_two)
print("\n=== After Registration ===")
print(f"Has listeners for 'app.start': {dispatcher.has_listeners('app.start')}")
# Get all listeners for an event
listeners = dispatcher.get_listeners("app.start")
print(f"Number of listeners: {len(listeners)}")
# Dispatch event
print("\n=== Dispatching Event ===")
dispatcher.dispatch("app.start")
# Remove a listener
print("\n=== After Removing listener_two ===")
dispatcher.remove_listener("app.start", listener_two)
print(f"Number of listeners: {len(dispatcher.get_listeners('app.start'))}")
# Dispatch again - only listener_one executes
print("\n=== Dispatching Event Again ===")
dispatcher.dispatch("app.start")
# Check if any events have listeners (v1.x compatibility)
print("\n=== Global Check ===")
print(f"Dispatcher has any listeners: {dispatcher.has_listeners()}")
if __name__ == "__main__":
main()
Running this produces:
=== Initial State ===
Has listeners for 'app.start': False
=== After Registration ===
Has listeners for 'app.start': True
Number of listeners: 2
=== Dispatching Event ===
Listener one executed
Listener two executed
=== After Removing listener_two ===
Number of listeners: 1
=== Dispatching Event Again ===
Listener one executed
=== Global Check ===
Dispatcher has any listeners: True
Use Cases¶
Dynamic Plugin Management¶
Load and unload plugins at runtime:
class Plugin:
def __init__(self, dispatcher):
self.dispatcher = dispatcher
self.listeners = []
def register(self):
listener = lambda event: self.on_event(event)
self.dispatcher.add_listener("app.event", listener)
self.listeners.append(("app.event", listener))
def unregister(self):
for event_id, listener in self.listeners:
self.dispatcher.remove_listener(event_id, listener)
self.listeners.clear()
# Install plugin
plugin = Plugin(dispatcher)
plugin.register()
# Uninstall plugin
plugin.unregister()
Conditional Dispatch¶
Optimize by checking before expensive operations:
def publish_metrics(metrics_data):
# Skip expensive serialization if no one is listening
if not dispatcher.has_listeners("metrics.publish"):
return
# Only create event if listeners exist
event = MetricsEvent(metrics_data)
dispatcher.dispatch("metrics.publish", event)
Testing and Debugging¶
Verify listener registration in tests:
def test_plugin_registers_listeners():
plugin = MyPlugin(dispatcher)
plugin.install()
# Verify listeners were registered
assert dispatcher.has_listeners("plugin.event")
listeners = dispatcher.get_listeners("plugin.event")
assert len(listeners) == 2
def test_plugin_cleanup():
plugin = MyPlugin(dispatcher)
plugin.install()
plugin.uninstall()
# Verify listeners were removed
assert not dispatcher.has_listeners("plugin.event")
Temporary Listeners¶
Add listeners for specific operations then remove them:
def temporary_operation():
# Add temporary logging
def temp_logger(event):
log.debug(f"Temporary log: {event.name}")
dispatcher.add_listener("operation.step", temp_logger)
try:
# Perform operation
perform_steps()
finally:
# Clean up
dispatcher.remove_listener("operation.step", temp_logger)
Important Notes¶
Reference Equality¶
remove_listener() uses reference equality. This won’t work:
# Add with lambda
dispatcher.add_listener("event", lambda e: print(e))
# Can't remove - different lambda object
dispatcher.remove_listener("event", lambda e: print(e)) # Error!
Store a reference to remove later:
listener = lambda e: print(e)
dispatcher.add_listener("event", listener)
dispatcher.remove_listener("event", listener) # Works
Removing During Dispatch¶
You can safely remove listeners during event dispatch, but they won’t affect the current dispatch cycle:
def self_removing_listener(event):
print("Executing once")
event.dispatcher.remove_listener("event", self_removing_listener)
dispatcher.add_listener("event", self_removing_listener)
dispatcher.dispatch("event") # Prints "Executing once"
dispatcher.dispatch("event") # Nothing happens (listener removed)
API Reference¶
has_listeners(event_id=None)Check if listeners are registered. Without
event_id, checks if any listeners exist.get_listeners(event_id=None)Get listeners for an event as a tuple. Without
event_id, returns dict of all events.remove_listener(event_id, listener)Remove a specific listener. Raises
ValueErrorif listener not found.
See also Design Patterns for practical examples of listener management.