diff --git a/IPython/core/events.py b/IPython/core/events.py index 798ef01..bfd09fe 100644 --- a/IPython/core/events.py +++ b/IPython/core/events.py @@ -69,7 +69,7 @@ class EventManager(object): Any additional arguments are passed to all callbacks registered for this event. Exceptions raised by callbacks are caught, and a message printed. """ - for func in self.callbacks[event]: + for func in self.callbacks[event][:]: try: func(*args, **kwargs) except Exception: diff --git a/IPython/core/tests/test_events.py b/IPython/core/tests/test_events.py index 8e8402c..3053a70 100644 --- a/IPython/core/tests/test_events.py +++ b/IPython/core/tests/test_events.py @@ -30,3 +30,25 @@ class CallbackTests(unittest.TestCase): self.em.register('ping_received', cb) with tt.AssertPrints("Error in callback"): self.em.trigger('ping_received') + + def test_unregister_during_callback(self): + invoked = [False] * 3 + + def func1(*_): + invoked[0] = True + self.em.unregister('ping_received', func1) + self.em.register('ping_received', func3) + + def func2(*_): + invoked[1] = True + self.em.unregister('ping_received', func2) + + def func3(*_): + invoked[2] = True + + self.em.register('ping_received', func1) + self.em.register('ping_received', func2) + + self.em.trigger('ping_received') + self.assertEqual([True, True, False], invoked) + self.assertEqual([func3], self.em.callbacks['ping_received']) diff --git a/docs/source/whatsnew/pr/incompat-event-triggering.rst b/docs/source/whatsnew/pr/incompat-event-triggering.rst new file mode 100644 index 0000000..432af68 --- /dev/null +++ b/docs/source/whatsnew/pr/incompat-event-triggering.rst @@ -0,0 +1,7 @@ +Update IPython event triggering to ensure callback registration and +unregistration only affects the set of callbacks the *next* time that event is +triggered. See :ghissue:`9447` and :ghpull:`9453`. + +This is a change to the existing semantics, wherein one callback registering a +second callback when triggered for an event would previously be invoked for +that same event.