Issue #22068: Avoided reference loops with Variables and Fonts in Tkinter.

This commit is contained in:
Serhiy Storchaka 2014-08-17 15:32:42 +03:00
commit 0879001f00
3 changed files with 40 additions and 15 deletions

View File

@ -191,6 +191,7 @@ class Variable:
that constrain the type of the value returned from get().""" that constrain the type of the value returned from get()."""
_default = "" _default = ""
_tk = None _tk = None
_tclCommands = None
def __init__(self, master=None, value=None, name=None): def __init__(self, master=None, value=None, name=None):
"""Construct a variable """Construct a variable
@ -209,7 +210,7 @@ class Variable:
global _varnum global _varnum
if not master: if not master:
master = _default_root master = _default_root
self._master = master self._root = master._root()
self._tk = master.tk self._tk = master.tk
if name: if name:
self._name = name self._name = name
@ -222,9 +223,15 @@ class Variable:
self.initialize(self._default) self.initialize(self._default)
def __del__(self): def __del__(self):
"""Unset the variable in Tcl.""" """Unset the variable in Tcl."""
if (self._tk is not None and if self._tk is None:
self._tk.getboolean(self._tk.call("info", "exists", self._name))): return
if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
self._tk.globalunsetvar(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): def __str__(self):
"""Return the name of the variable in Tcl.""" """Return the name of the variable in Tcl."""
return self._name return self._name
@ -244,7 +251,20 @@ class Variable:
Return the name of the callback. 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) self._tk.call("trace", "variable", self._name, mode, cbname)
return cbname return cbname
trace = trace_variable trace = trace_variable
@ -255,7 +275,11 @@ class Variable:
CBNAME is the name of the callback returned from trace_variable or trace. CBNAME is the name of the callback returned from trace_variable or trace.
""" """
self._tk.call("trace", "vdelete", self._name, mode, cbname) 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): def trace_vinfo(self):
"""Return all trace callback information.""" """Return all trace callback information."""
return [self._tk.split(x) for x in self._tk.splitlist( return [self._tk.split(x) for x in self._tk.splitlist(

View File

@ -69,9 +69,10 @@ class Font:
**options): **options):
if not root: if not root:
root = tkinter._default_root root = tkinter._default_root
tk = getattr(root, 'tk', root)
if font: if font:
# get actual settings corresponding to the given 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: else:
font = self._set(options) font = self._set(options)
if not name: if not name:
@ -81,21 +82,19 @@ class Font:
if exists: if exists:
self.delete_font = False self.delete_font = False
# confirm font exists # confirm font exists
if self.name not in root.tk.splitlist( if self.name not in tk.splitlist(tk.call("font", "names")):
root.tk.call("font", "names")):
raise tkinter._tkinter.TclError( raise tkinter._tkinter.TclError(
"named font %s does not already exist" % (self.name,)) "named font %s does not already exist" % (self.name,))
# if font config info supplied, apply it # if font config info supplied, apply it
if font: if font:
root.tk.call("font", "configure", self.name, *font) tk.call("font", "configure", self.name, *font)
else: else:
# create new font (raises TclError if the font exists) # 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 self.delete_font = True
# backlinks! self._tk = tk
self._root = root self._split = tk.splitlist
self._split = root.tk.splitlist self._call = tk.call
self._call = root.tk.call
def __str__(self): def __str__(self):
return self.name return self.name
@ -120,7 +119,7 @@ class Font:
def copy(self): def copy(self):
"Return a distinct copy of the current font" "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): def actual(self, option=None, displayof=None):
"Return actual font attributes" "Return actual font attributes"

View File

@ -118,6 +118,8 @@ Core and Builtins
Library Library
------- -------
- Issue #22068: Avoided reference loops with Variables and Fonts in Tkinter.
- Issue #22165: SimpleHTTPRequestHandler now supports undecodable file names. - Issue #22165: SimpleHTTPRequestHandler now supports undecodable file names.
- Issue #15381: Optimized line reading in io.BytesIO. - Issue #15381: Optimized line reading in io.BytesIO.