Repository navigation
Tkinter widget.unbind(sequence, funcid) unbind all bindings #75666
Description
Activity
I am using python 3.6.2 with tk 8.6.7 in Linux and when I call widget.unbind(<Sequence>, funcid), it unbinds all bindings for <Sequence>, while I would expect it to unbind only funcid. Here is an example reproducing the problem:
import tkinter as tk root = tk.Tk() def cmd1(event): print('1') def cmd2(event): print('2') def unbind1(): l.unbind('<Motion>', b1) def unbind2(): l.unbind('<Motion>', b2) l = tk.Label(root, text='Hover') b1 = l.bind('<Motion>', cmd1) b2 = l.bind('<Motion>', cmd2, True) l.pack() tk.Button(root, text='Unbind 1', command=unbind1).pack() tk.Button(root, text='Unbind 2', command=unbind2).pack() root.mainloop()
In this example, clicking on one of the buttons unbinds both bindings instead of only one.
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Sep 15, 2017 I have found a workaround to unbind a single binding (inspired by https://mail.python.org/pipermail/tkinter-discuss/2012-May/003151.html):
def unbind(widget, seq, funcid): bindings = {x.split()[1][3:]: x for x in widget.bind(seq).splitlines() if x.strip()} try: del bindings[funcid] except KeyError: raise tk.TclError('Binding "%s" not defined.' % funcid) widget.bind(seq, '\n'.join(list(bindings.values())))
Reacted by antonin-acdTk do not provide a way of unbinding a specific command. It supports binding a script with replacing an existing binding, appending a script to an existing binding, and destroying an existing binding. Unbinding a specific command can be implemented in Python with a code similar to your example, but more complex due to taking to account different corner cases.
Do you want to write a patch and open a pull request Juliette?
The unbind docstring, "Unbind for this widget for event SEQUENCE the
function identified with FUNCID" implies to me the behavior Juliette expected. The NMT reference unpacks this to two sentences. Reading the code
self.tk.call('bind', self._w, sequence, '')
if funcid:
self.deletecommand(funcid)the docstring should be something like "Unbind for this widget the event SEQUENCE. If funcid is given, delete the command also" This part of the bind docstring, "Bind will return an identifier to allow deletion of the bound function with unbind without memory leak." implies that the unbind behavior is deliberate.
Serhiy, are you proposing to add a new method that does what some people thought unbind would do?
- added3.7 (EOL)end of lifeend of lifetype-featureA feature request or enhancementA feature request or enhancementand removedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Sep 15, 2017 I don't think a new method is needed. We can make unbind() destroying all bindings if FUNCID is not given (note that registered commands are leaked in this case), and only the specified command if it is specified. We can add a boolean parameter (similar to the one in bind()) for restoring the old behavior if this is needed.
I don't mind writing a patch and opening a pull request, however I don't know which corner cases I need to take into account.
I propose the below fix:
def unbind(self, sequence, funcid=None): """Unbind for this widget for event SEQUENCE the function identified with FUNCID.""" bound = '' if funcid: self.deletecommand(funcid) funcs = self.tk.call('bind', self._w, sequence, None).split('\n') bound = '\n'.join([f for f in funcs if not f.startswith('if {{"[{0}'.format(funcid))]) self.tk.call('bind', self._w, sequence, bound)Serhiy, if I understand your last message (msg302299), the docstring should read:
Unbind for this widget the event SEQUENCE. If FUNCID is given, only unbind the function identified with FUNCID and also delete that command.The main doc change is to specify what happens when funcid is not given while the code change in the patch is to no longer unbind everything when funcid *is* given.
Serhiy, given that the code did not match the docstring and that we are changing the code to match the clarified docstring, should we consider this a bugfix, and, should we backport it?
29 remaining items
serhiy-storchaka commented
on Jan 11, 2024 on Jan 11, 2024 · Hidden as resolvedshow commentMore actions- addedextension-modulesC modules in the Modules dirC modules in the Modules dir
on Jun 20, 2026 Opened #151808.
I wrote it almost 3 years ago, but there was a little issue with IDLE, so I put it aside and then forgot about it. Turned out, the bug was in IDLE tests. It was hidden by that bug in unbind() and was exposed when it was fixed. The change does not affect IDLE, and I hope it will not break much user code.
Still, it is safer to only backport this fix to 3.15. The leak was always.
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
Linked PRs