From 87bbf257ef6e8d1e5876776094db609702ee468a Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Sun, 17 Aug 2014 15:31:59 +0300
Subject: Issue #22068: Avoided reference loops with Variables and Fonts in
 Tkinter.

---
 Lib/tkinter/__init__.py | 34 +++++++++++++++++++++++++++++-----
 Lib/tkinter/font.py     | 19 +++++++++----------
 Misc/NEWS               |  2 ++
 3 files changed, 40 insertions(+), 15 deletions(-)

diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py
index 75e5fb1..5447732 100644
--- a/Lib/tkinter/__init__.py
+++ b/Lib/tkinter/__init__.py
@@ -191,6 +191,7 @@ class Variable:
     that constrain the type of the value returned from get()."""
     _default = ""
     _tk = None
+    _tclCommands = None
     def __init__(self, master=None, value=None, name=None):
         """Construct a variable
 
@@ -209,7 +210,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
@@ -222,9 +223,15 @@ class Variable:
             self.initialize(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
@@ -244,7 +251,20 @@ class Variable:
 
         Return the name of the callback.
         """
-        cbname = self._master._register(callback)
+        f = CallWrapper(callback, None, self).__call__
+        cbname = repr(id(f))
+        try:
+            callback = callback.__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
@@ -255,7 +275,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 [self._tk.split(x) for x in self._tk.splitlist(
diff --git a/Lib/tkinter/font.py b/Lib/tkinter/font.py
index 2096093..b966732 100644
--- a/Lib/tkinter/font.py
+++ b/Lib/tkinter/font.py
@@ -69,9 +69,10 @@ class Font:
                  **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:
@@ -81,21 +82,19 @@ 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
@@ -120,7 +119,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, displayof=None):
         "Return actual font attributes"
diff --git a/Misc/NEWS b/Misc/NEWS
index 15f2b82..58d23dd 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -27,6 +27,8 @@ Core and Builtins
 Library
 -------
 
+- Issue #22068: Avoided reference loops with Variables and Fonts in Tkinter.
+
 - Issue #22165: SimpleHTTPRequestHandler now supports undecodable file names.
 
 - Issue #20729: Restored the use of lazy iterkeys()/itervalues()/iteritems()
-- 
cgit v0.12