Serhiy Storchaka added the comment:
> "list or tuple" (of such strings.) Can we say 'sequence' (or even set or
> iterable) or is the requirement specifically tuple or list?
No. The call() method accepted only tuples, and since issue21525 it now
supports lists.
> Does tk call the traceback function with any args it supplies(other than
> those passed in)? (In other words, is the callback affected by the #22214
> proposal?)
Tcl calls the traceback function with stringified arguments. No, this is not
affected by the #22214 proposal. And only types which are supported by Tkinter
(int, bool, float, str, bytes, tuple, list, Tcl_Obj) are allowed. But this
behavior can be changed in future versions of Tcl..
> "Should be same as were specified in trace_add()." Is a subset not allowed
> to remove a subset of the registrations currently allowed? Does trying to
> delete other modes raise TclError? If both are so, I would say something
> like "Must be a subset of current trace modes.".
Unfortunately both are no. If you pass wrong mode, trace_remove() will
silently finished, but next attempt to call a callback will raise TclError
(because a command is already deleted). Fixed in next patch and should be
backported.
> That aside, remembering and passing the
> arguments back seems like a bizarre requirement that makes not sense. What
> happens if one does not, or gets it wrong?
Same bad things as when pass wrong mode.
> It the args are really required
> exactly, then is seems that trace_add should return (cbname,) + args to be
> passed back to trace_remove as cbname_args.
Looks good.
> Alternatively, could *args (and mode) be retrieved from trace_info?
You can retrieve mode, cbname and (stringified) args from trace_info. Currently
following code removes all callbacks from variable:
for mode, cbname_args in v.trace_info():
v.trace_remove(mode, *cbname_args)
With next patch '*' is not needed.
Thank you for the review. Updated patch addresses all your comments.
----------
Added file: http://bugs.python.org/file36404/tkinter_trace_variable_4.patch
_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue22115>
_______________________________________
diff -r 66c7f30fe8c7 Lib/tkinter/__init__.py
--- a/Lib/tkinter/__init__.py Sun Aug 17 23:01:33 2014 -0500
+++ b/Lib/tkinter/__init__.py Mon Aug 18 15:37:33 2014 +0300
@@ -242,16 +242,9 @@
def get(self):
"""Return value of variable."""
return self._tk.globalgetvar(self._name)
- def trace_variable(self, mode, callback):
- """Define a trace callback for the variable.
-
- MODE is one of "r", "w", "u" for read, write, undefine.
- CALLBACK must be a function which is called when
- the variable is read, written or undefined.
-
- Return the name of the callback.
- """
- f = CallWrapper(callback, None, self).__call__
+
+ def _register(self, callback):
+ f = CallWrapper(callback, None, self._root).__call__
cbname = repr(id(f))
try:
callback = callback.__func__
@@ -265,6 +258,65 @@
if self._tclCommands is None:
self._tclCommands = []
self._tclCommands.append(cbname)
+ return cbname
+
+ def trace_add(self, mode, callback, *args):
+ """Define a trace callback for the variable.
+
+ Mode is one of "array", "read", "write", "unset", or a list or tuple
+ of such strings.
+ Callback must be a function which is called when the variable is
+ accessed or modified via the array command, read, written or unset.
+ Additional arguments are passed to the traceback call.
+
+ Return a tuple containg the name of the callback and additional
+ arguments.
+ """
+ cbname = self._register(callback)
+ self._tk.call('trace', 'add', 'variable',
+ self._name, mode, (cbname,) + args)
+ return (cbname,) + args
+
+ def trace_remove(self, mode, cbname_args):
+ """Delete the trace callback for a variable.
+
+ Mode is one of "array", "read", "write", "unset" or a list or tuple
+ of such strings. Must be same as were specified in trace_add().
+ Cbname_args must be a tuple returned from trace_add().
+ """
+ self._tk.call('trace', 'remove', 'variable',
+ self._name, mode, cbname_args)
+ cbname = cbname_args[0]
+ for m, ca in self.trace_info():
+ if ca[0] == cbname:
+ break
+ else:
+ self._tk.deletecommand(cbname)
+ try:
+ self._tclCommands.remove(cbname)
+ except ValueError:
+ pass
+
+ def trace_info(self):
+ """Return all trace callback information."""
+ splitlist = self._tk.splitlist
+ return [(splitlist(k), splitlist(v)) for k, v in map(splitlist,
+ splitlist(self._tk.call('trace', 'info', 'variable', self._name)))]
+
+ def trace_variable(self, mode, callback):
+ """Define a trace callback for the variable.
+
+ MODE is one of "r", "w", "u" for read, write, undefine.
+ CALLBACK must be a function which is called when
+ the variable is read, written or undefined.
+
+ Return the name of the callback.
+
+ This deprecated method wraps a deprecated Tcl method that will
+ likely be removed in the future. Use trace_add() instead.
+ """
+ # TODO: Add deprecation warning
+ cbname = self._register(callback)
self._tk.call("trace", "variable", self._name, mode, cbname)
return cbname
trace = trace_variable
@@ -273,15 +325,29 @@
MODE is one of "r", "w", "u" for read, write, undefine.
CBNAME is the name of the callback returned from trace_variable or
trace.
+
+ This deprecated method wraps a deprecated Tcl method that will
+ likely be removed in the future. Use trace_remove() instead.
"""
+ # TODO: Add deprecation warning
self._tk.call("trace", "vdelete", self._name, mode, cbname)
- self._tk.deletecommand(cbname)
- try:
- self._tclCommands.remove(cbname)
- except ValueError:
- pass
+ cbname = self._tk.splitlist(cbname)[0]
+ for m, ca in self.trace_info():
+ if self._tk.splitlist(ca)[0] == cbname:
+ break
+ else:
+ self._tk.deletecommand(cbname)
+ try:
+ self._tclCommands.remove(cbname)
+ except ValueError:
+ pass
def trace_vinfo(self):
- """Return all trace callback information."""
+ """Return all trace callback information.
+
+ This deprecated method wraps a deprecated Tcl method that will
+ likely be removed in the future. Use trace_info() instead.
+ """
+ # TODO: Add deprecation warning
return [self._tk.split(x) for x in self._tk.splitlist(
self._tk.call("trace", "vinfo", self._name))]
def __eq__(self, other):
diff -r 66c7f30fe8c7 Lib/tkinter/test/test_tkinter/test_variables.py
--- a/Lib/tkinter/test/test_tkinter/test_variables.py Sun Aug 17 23:01:33
2014 -0500
+++ b/Lib/tkinter/test/test_tkinter/test_variables.py Mon Aug 18 15:37:33
2014 +0300
@@ -1,6 +1,6 @@
import unittest
-
-from tkinter import Variable, StringVar, IntVar, DoubleVar, BooleanVar, Tk
+import gc
+from tkinter import Variable, StringVar, IntVar, DoubleVar, BooleanVar, Tk,
TclError
class Var(Variable):
@@ -86,6 +86,106 @@
v.set("value")
self.assertTrue(v.side_effect)
+ def test_trace_old(self):
+ v = Var()
+ vname = str(v)
+ trace = []
+ def read_tracer(*args):
+ trace.append(('read',) + args)
+ def write_tracer(*args):
+ trace.append(('write',) + args)
+ cb1 = v.trace_variable('r', read_tracer)
+ cb2 = v.trace_variable('wu', write_tracer)
+ self.assertEqual(sorted(v.trace_vinfo()), [('r', cb1), ('wu', cb2)])
+ self.assertEqual(trace, [])
+
+ v.set('spam')
+ self.assertEqual(trace, [('write', vname, '', 'w')])
+
+ trace = []
+ v.get()
+ self.assertEqual(trace, [('read', vname, '', 'r')])
+
+ trace = []
+ info = sorted(v.trace_vinfo())
+ v.trace_vdelete('w', cb1) # Wrong mode
+ self.assertEqual(sorted(v.trace_vinfo()), info)
+ with self.assertRaises(TclError):
+ v.trace_vdelete('r', 'spam') # Wrong command name
+ self.assertEqual(sorted(v.trace_vinfo()), info)
+ v.trace_vdelete('r', (cb1, 43)) # Wrong arguments
+ self.assertEqual(sorted(v.trace_vinfo()), info)
+ v.get()
+ self.assertEqual(trace, [('read', vname, '', 'r')])
+
+ trace = []
+ v.trace_vdelete('r', cb1)
+ self.assertEqual(v.trace_vinfo(), [('wu', cb2)])
+ v.get()
+ self.assertEqual(trace, [])
+
+ trace = []
+ del write_tracer
+ gc.collect()
+ v.set('eggs')
+ self.assertEqual(trace, [('write', vname, '', 'w')])
+
+ trace = []
+ del v
+ gc.collect()
+ self.assertEqual(trace, [('write', vname, '', 'u')])
+
+ def test_trace(self):
+ v = Var()
+ vname = str(v)
+ trace = []
+ def read_tracer(*args):
+ trace.append(('read',) + args)
+ def write_tracer(*args):
+ trace.append(('write',) + args)
+ tr1 = v.trace_add('read', read_tracer, 42)
+ tr2 = v.trace_add(['write', 'unset'], write_tracer)
+ self.assertEqual(sorted(v.trace_info()), [
+ (('read',), (tr1[0], '42')),
+ (('write', 'unset'), tr2)])
+ self.assertEqual(trace, [])
+
+ v.set('spam')
+ self.assertEqual(trace, [('write', vname, '', 'write')])
+
+ trace = []
+ v.get()
+ self.assertEqual(trace, [('read', '42', vname, '', 'read')])
+
+ trace = []
+ info = sorted(v.trace_info())
+ v.trace_remove('write', tr1) # Wrong mode
+ self.assertEqual(sorted(v.trace_info()), info)
+ with self.assertRaises(TclError):
+ v.trace_remove('read', ('spam',)) # Wrong command name
+ self.assertEqual(sorted(v.trace_info()), info)
+ v.trace_remove('read', (tr1[0], 43)) # Wrong arguments
+ self.assertEqual(sorted(v.trace_info()), info)
+ v.get()
+ self.assertEqual(trace, [('read', '42', vname, '', 'read')])
+
+ trace = []
+ v.trace_remove('read', tr1)
+ self.assertEqual(v.trace_info(), [(('write', 'unset'), tr2)])
+ v.get()
+ self.assertEqual(trace, [])
+
+ trace = []
+ del write_tracer
+ gc.collect()
+ v.set('eggs')
+ self.assertEqual(trace, [('write', vname, '', 'write')])
+
+ trace = []
+ del v
+ gc.collect()
+ self.assertEqual(trace, [('write', vname, '', 'unset')])
+
class TestStringVar(TestBase):
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com