From 44ae51347c7b4eba98e6593a8c6539f7a4a870ab Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 17 Aug 2014 15:31:41 +0300 Subject: [PATCH] Issue #22068: Avoided reference loops with Variables and Fonts in Tkinter. --- Lib/lib-tk/Tkinter.py | 34 +++++++++++++++++++++++++++++----- Lib/lib-tk/tkFont.py | 19 +++++++++---------- Misc/NEWS | 2 ++ 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/Lib/lib-tk/Tkinter.py b/Lib/lib-tk/Tkinter.py index aade6491cc..bf5bc2881a 100644 --- a/Lib/lib-tk/Tkinter.py +++ b/Lib/lib-tk/Tkinter.py @@ -201,6 +201,7 @@ class Variable: Subclasses StringVar, IntVar, DoubleVar, BooleanVar are specializations that constrain the type of the value returned from get().""" _default = "" + _tclCommands = None def __init__(self, master=None, value=None, name=None): """Construct a variable @@ -214,7 +215,7 @@ class Variable: global _varnum if not master: master = _default_root - self._master = master + self._root = master._root() self._tk = master.tk if name: self._name = name @@ -227,9 +228,15 @@ class Variable: self.set(self._default) def __del__(self): """Unset the variable in Tcl.""" - if (self._tk is not None and - self._tk.getboolean(self._tk.call("info", "exists", self._name))): + if self._tk is None: + return + if self._tk.getboolean(self._tk.call("info", "exists", self._name)): self._tk.globalunsetvar(self._name) + if self._tclCommands is not None: + for name in self._tclCommands: + #print '- Tkinter: deleted command', name + self._tk.deletecommand(name) + self._tclCommands = None def __str__(self): """Return the name of the variable in Tcl.""" return self._name @@ -248,7 +255,20 @@ class Variable: Return the name of the callback. """ - cbname = self._master._register(callback) + f = CallWrapper(callback, None, self._root).__call__ + cbname = repr(id(f)) + try: + callback = callback.im_func + except AttributeError: + pass + try: + cbname = cbname + callback.__name__ + except AttributeError: + pass + self._tk.createcommand(cbname, f) + if self._tclCommands is None: + self._tclCommands = [] + self._tclCommands.append(cbname) self._tk.call("trace", "variable", self._name, mode, cbname) return cbname trace = trace_variable @@ -259,7 +279,11 @@ class Variable: CBNAME is the name of the callback returned from trace_variable or trace. """ self._tk.call("trace", "vdelete", self._name, mode, cbname) - self._master.deletecommand(cbname) + self._tk.deletecommand(cbname) + try: + self._tclCommands.remove(cbname) + except ValueError: + pass def trace_vinfo(self): """Return all trace callback information.""" return map(self._tk.split, self._tk.splitlist( diff --git a/Lib/lib-tk/tkFont.py b/Lib/lib-tk/tkFont.py index dfdee425f3..113c983b01 100644 --- a/Lib/lib-tk/tkFont.py +++ b/Lib/lib-tk/tkFont.py @@ -66,9 +66,10 @@ class Font: def __init__(self, root=None, font=None, name=None, exists=False, **options): if not root: root = Tkinter._default_root + tk = getattr(root, 'tk', root) if font: # get actual settings corresponding to the given font - font = root.tk.splitlist(root.tk.call("font", "actual", font)) + font = tk.splitlist(tk.call("font", "actual", font)) else: font = self._set(options) if not name: @@ -78,20 +79,18 @@ class Font: if exists: self.delete_font = False # confirm font exists - if self.name not in root.tk.splitlist( - root.tk.call("font", "names")): + if self.name not in tk.splitlist(tk.call("font", "names")): raise Tkinter._tkinter.TclError, "named font %s does not already exist" % (self.name,) # if font config info supplied, apply it if font: - root.tk.call("font", "configure", self.name, *font) + tk.call("font", "configure", self.name, *font) else: # create new font (raises TclError if the font exists) - root.tk.call("font", "create", self.name, *font) + tk.call("font", "create", self.name, *font) self.delete_font = True - # backlinks! - self._root = root - self._split = root.tk.splitlist - self._call = root.tk.call + self._tk = tk + self._split = tk.splitlist + self._call = tk.call def __str__(self): return self.name @@ -116,7 +115,7 @@ class Font: def copy(self): "Return a distinct copy of the current font" - return Font(self._root, **self.actual()) + return Font(self._tk, **self.actual()) def actual(self, option=None): "Return actual font attributes" diff --git a/Misc/NEWS b/Misc/NEWS index 5f731bf7ec..937d10340a 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -19,6 +19,8 @@ Core and Builtins Library ------- +- Issue #22068: Avoided reference loops with Variables and Fonts in Tkinter. + - Issue #21448: Changed FeedParser feed() to avoid O(N**2) behavior when parsing long line. Original patch by Raymond Hettinger. -- 2.50.1