Repository: pytorch/ignite

Issue:
Remove handler feature in Engine
I recently encountered the cases where I would like to remove a handler and optionally replace it with another one. The following code is used:
```python
def remove_handler(engine, handler, event_name):
    assert event_name in engine._event_handlers    
    engine._event_handlers[event_name] = [(h, args, kwargs) 
                                          for h, args, kwargs in engine._event_handlers[event_name] 
                                            if h != handler]
```

@alykhantejani @jasonkriss what do you guys think about inserting such method into `Engine` ?
cc @anmolsjoshi 

Discussion:
vfdev-5: @anmolsjoshi or @jasonkriss please merge if OK

Patch:
diff --git a/ignite/engine/engine.py b/ignite/engine/engine.py
index e6f9620a..07fbf8c5 100644
--- a/ignite/engine/engine.py
+++ b/ignite/engine/engine.py
@@ -163,6 +163,23 @@ class Engine(object):
                     return True
         return False
 
+    def remove_event_handler(self, handler, event_name):
+        """Remove event handler `handler` from registered handlers of the engine
+
+        Args:
+            handler (callable): the callable event handler that should be removed
+            event_name: The event the handler attached to.
+
+        """
+        if event_name not in self._event_handlers:
+            raise ValueError("Input event name '{}' does not exist".format(event_name))
+
+        new_event_handlers = [(h, args, kwargs) for h, args, kwargs in self._event_handlers[event_name]
+                              if h != handler]
+        if len(new_event_handlers) == len(self._event_handlers[event_name]):
+            raise ValueError("Input handler '{}' is not found among registered event handlers".format(handler))
+        self._event_handlers[event_name] = new_event_handlers
+
     def _check_signature(self, fn, fn_description, *args, **kwargs):
         exception_msg = None
 


Test patch:
diff --git a/tests/ignite/engine/test_engine.py b/tests/ignite/engine/test_engine.py
index a3f1d64c..3b9f19fa 100644
--- a/tests/ignite/engine/test_engine.py
+++ b/tests/ignite/engine/test_engine.py
@@ -150,6 +150,38 @@ def test_has_event_handler():
     assert not engine.has_event_handler(m, Events.EPOCH_STARTED)
 
 
+def test_remove_event_handler():
+    engine = DummyEngine()
+
+    with pytest.raises(ValueError, match=r'Input event name'):
+        engine.remove_event_handler(lambda x: x, "an event")
+
+    def on_started(engine):
+        return 0
+
+    engine.add_event_handler(Events.STARTED, on_started)
+
+    with pytest.raises(ValueError, match=r'Input handler'):
+        engine.remove_event_handler(lambda x: x, Events.STARTED)
+
+    h1 = MagicMock()
+    h2 = MagicMock()
+    handlers = [h1, h2]
+    m = MagicMock()
+    for handler in handlers:
+        engine.add_event_handler(Events.EPOCH_STARTED, handler)
+    engine.add_event_handler(Events.EPOCH_COMPLETED, m)
+
+    assert len(engine._event_handlers[Events.EPOCH_STARTED]) == 2
+    engine.remove_event_handler(h1, Events.EPOCH_STARTED)
+    assert len(engine._event_handlers[Events.EPOCH_STARTED]) == 1
+    assert engine._event_handlers[Events.EPOCH_STARTED][0][0] == h2
+
+    assert len(engine._event_handlers[Events.EPOCH_COMPLETED]) == 1
+    engine.remove_event_handler(m, Events.EPOCH_COMPLETED)
+    assert len(engine._event_handlers[Events.EPOCH_COMPLETED]) == 0
+
+
 def test_args_and_kwargs_are_passed_to_event():
     engine = DummyEngine()
     kwargs = {'a': 'a', 'b': 'b'}