Description
Currently, the docstrings of TimerBase.add_callback and remove_callback read,
respectively
Register `func` to be called by timer when the event fires. Any
additional arguments provided will be passed to `func`.
and
Remove `func` from list of callbacks. `args` and `kwargs` are optional
and used to distinguish between copies of the same function registered
to be called with different arguments.
AFAICT, the implementations match the behavior, but importantly, if called with
no args/kwargs, then remove_callback removes add instances where the function
has been added regardless of the presence of args/kwargs (hinted at by the
docstring, though possibly not obvious).
This means that if you have
def f(x=0): ...
timer.add_callback(f)
timer.add_callback(f, 1)
then you cannot remove the first callback without removing the second callback.
Compare with
def f(x=0): ...
timer.add_callback(f, 0)
timer.add_callback(f, 1)
where you can.
What can be changed to make things more consistent? On the one hand one could
say that remove_callback only removes complete matches (f, args, kwargs), i.e.
remove_callback(f)
would undo add_callback(f)
but not add_callback(f, 1)
.
Alternatively, we can look into the stdlib's atexit.{register,unregister} which
needs to solve essentially the same semantics problem. There, the choice that
was made was for atexit.unregister to only take the function as argument, and
remove all instances of that function regardless of args/kwargs; if one wants
to distinguish between them for removal one can always pass in a partial(f,
...) object; note that different instances of partial objects always compare
inequal even when the constructor is called twice with the same parameters.
(This is made simpler by atexit.register returning its first argument (for use
as a decorator), so one can do p = atexit.register(partial(f, ...))
to later
atexit.unregister(p)
.