Issue #22226: Added private function _splitdict() in the Tkinter module.
First letter no longer is stripped from the "status" key in
the result of Treeview.heading().
diff --git a/Lib/lib-tk/Tkinter.py b/Lib/lib-tk/Tkinter.py
index 76fbc07..4de2605 100644
--- a/Lib/lib-tk/Tkinter.py
+++ b/Lib/lib-tk/Tkinter.py
@@ -123,6 +123,29 @@
try: _cnfmerge = _tkinter._cnfmerge
except AttributeError: pass
+def _splitdict(tk, v, cut_minus=True, conv=None):
+ """Return a properly formatted dict built from Tcl list pairs.
+
+ If cut_minus is True, the supposed '-' prefix will be removed from
+ keys. If conv is specified, it is used to convert values.
+
+ Tcl list is expected to contain an even number of elements.
+ """
+ t = tk.splitlist(v)
+ if len(t) % 2:
+ raise RuntimeError('Tcl list representing a dict is expected '
+ 'to contain an even number of elements')
+ it = iter(t)
+ dict = {}
+ for key, value in zip(it, it):
+ key = str(key)
+ if cut_minus and key[0] == '-':
+ key = key[1:]
+ if conv:
+ value = conv(value)
+ dict[key] = value
+ return dict
+
class Event:
"""Container for the properties of an event.
@@ -1390,15 +1413,10 @@
else:
options = self._options(cnf, kw)
if not options:
- res = self.tk.call('grid',
- command, self._w, index)
- words = self.tk.splitlist(res)
- dict = {}
- for i in range(0, len(words), 2):
- key = words[i][1:]
- value = words[i+1]
- dict[key] = self._gridconvvalue(value)
- return dict
+ return _splitdict(
+ self.tk,
+ self.tk.call('grid', command, self._w, index),
+ conv=self._gridconvvalue)
res = self.tk.call(
('grid', command, self._w, index)
+ options)
@@ -1921,16 +1939,10 @@
def pack_info(self):
"""Return information about the packing options
for this widget."""
- words = self.tk.splitlist(
- self.tk.call('pack', 'info', self._w))
- dict = {}
- for i in range(0, len(words), 2):
- key = words[i][1:]
- value = words[i+1]
- if str(value)[:1] == '.':
- value = self._nametowidget(value)
- dict[key] = value
- return dict
+ d = _splitdict(self.tk, self.tk.call('pack', 'info', self._w))
+ if 'in' in d:
+ d['in'] = self.nametowidget(d['in'])
+ return d
info = pack_info
propagate = pack_propagate = Misc.pack_propagate
slaves = pack_slaves = Misc.pack_slaves
@@ -1972,16 +1984,10 @@
def place_info(self):
"""Return information about the placing options
for this widget."""
- words = self.tk.splitlist(
- self.tk.call('place', 'info', self._w))
- dict = {}
- for i in range(0, len(words), 2):
- key = words[i][1:]
- value = words[i+1]
- if str(value)[:1] == '.':
- value = self._nametowidget(value)
- dict[key] = value
- return dict
+ d = _splitdict(self.tk, self.tk.call('place', 'info', self._w))
+ if 'in' in d:
+ d['in'] = self.nametowidget(d['in'])
+ return d
info = place_info
slaves = place_slaves = Misc.place_slaves
@@ -2021,16 +2027,10 @@
def grid_info(self):
"""Return information about the options
for positioning this widget in a grid."""
- words = self.tk.splitlist(
- self.tk.call('grid', 'info', self._w))
- dict = {}
- for i in range(0, len(words), 2):
- key = words[i][1:]
- value = words[i+1]
- if str(value)[:1] == '.':
- value = self._nametowidget(value)
- dict[key] = value
- return dict
+ d = _splitdict(self.tk, self.tk.call('grid', 'info', self._w))
+ if 'in' in d:
+ d['in'] = self.nametowidget(d['in'])
+ return d
info = grid_info
location = grid_location = Misc.grid_location
propagate = grid_propagate = Misc.grid_propagate
diff --git a/Lib/lib-tk/test/test_ttk/test_functions.py b/Lib/lib-tk/test/test_ttk/test_functions.py
index 50c5aeb..714d950 100644
--- a/Lib/lib-tk/test/test_ttk/test_functions.py
+++ b/Lib/lib-tk/test/test_ttk/test_functions.py
@@ -324,26 +324,13 @@
"-opt {3 2m}")
- def test_dict_from_tcltuple(self):
- fakettuple = ('-a', '{1 2 3}', '-something', 'foo')
-
- self.assertEqual(ttk._dict_from_tcltuple(fakettuple, False),
- {'-a': '{1 2 3}', '-something': 'foo'})
-
- self.assertEqual(ttk._dict_from_tcltuple(fakettuple),
- {'a': '{1 2 3}', 'something': 'foo'})
-
- # passing a tuple with a single item should return an empty dict,
- # since it tries to break the tuple by pairs.
- self.assertFalse(ttk._dict_from_tcltuple(('single', )))
-
- sspec = MockStateSpec('a', 'b')
- self.assertEqual(ttk._dict_from_tcltuple(('-a', (sspec, 'val'))),
- {'a': [('a', 'b', 'val')]})
-
- self.assertEqual(ttk._dict_from_tcltuple((MockTclObj('-padding'),
- [MockTclObj('1'), 2, MockTclObj('3m')])),
- {'padding': [1, 2, '3m']})
+ def test_tclobj_to_py(self):
+ self.assertEqual(
+ ttk._tclobj_to_py((MockStateSpec('a', 'b'), 'val')),
+ [('a', 'b', 'val')])
+ self.assertEqual(
+ ttk._tclobj_to_py([MockTclObj('1'), 2, MockTclObj('3m')]),
+ [1, 2, '3m'])
def test_list_from_statespec(self):
diff --git a/Lib/lib-tk/ttk.py b/Lib/lib-tk/ttk.py
index 77f1d3a..5ed018a 100644
--- a/Lib/lib-tk/ttk.py
+++ b/Lib/lib-tk/ttk.py
@@ -26,7 +26,7 @@
"tclobjs_to_py", "setup_master"]
import Tkinter
-from Tkinter import _flatten, _join, _stringify
+from Tkinter import _flatten, _join, _stringify, _splitdict
# Verify if Tk is new enough to not need the Tile package
_REQUIRE_TILE = True if Tkinter.TkVersion < 8.5 else False
@@ -242,21 +242,6 @@
return '\n'.join(script)
-def _dict_from_tcltuple(ttuple, cut_minus=True):
- """Break tuple in pairs, format it properly, then build the return
- dict. If cut_minus is True, the supposed '-' prefixing options will
- be removed.
-
- ttuple is expected to contain an even number of elements."""
- opt_start = 1 if cut_minus else 0
-
- retdict = {}
- it = iter(ttuple)
- for opt, val in zip(it, it):
- retdict[str(opt)[opt_start:]] = val
-
- return tclobjs_to_py(retdict)
-
def _list_from_statespec(stuple):
"""Construct a list from the given statespec tuple according to the
accepted statespec accepted by _format_mapdict."""
@@ -316,7 +301,7 @@
if len(options) % 2: # option specified without a value, return its value
return res
- return _dict_from_tcltuple(tk.splitlist(res))
+ return _splitdict(tk, res, conv=_tclobj_to_py)
def _convert_stringval(value):
"""Converts a value to, hopefully, a more appropriate Python object."""
@@ -336,20 +321,24 @@
x = int(x)
return x
+def _tclobj_to_py(val):
+ """Return value converted from Tcl object to Python object."""
+ if val and hasattr(val, '__len__') and not isinstance(val, basestring):
+ if getattr(val[0], 'typename', None) == 'StateSpec':
+ val = _list_from_statespec(val)
+ else:
+ val = map(_convert_stringval, val)
+
+ elif hasattr(val, 'typename'): # some other (single) Tcl object
+ val = _convert_stringval(val)
+
+ return val
+
def tclobjs_to_py(adict):
"""Returns adict with its values converted from Tcl objects to Python
objects."""
- for opt, val in adict.iteritems():
- if val and hasattr(val, '__len__') and not isinstance(val, basestring):
- if getattr(val[0], 'typename', None) == 'StateSpec':
- val = _list_from_statespec(val)
- else:
- val = map(_convert_stringval, val)
-
- elif hasattr(val, 'typename'): # some other (single) Tcl object
- val = _convert_stringval(val)
-
- adict[opt] = val
+ for opt, val in adict.items():
+ adict[opt] = _tclobj_to_py(val)
return adict
@@ -409,8 +398,10 @@
return _list_from_statespec(self.tk.splitlist(
self.tk.call(self._name, "map", style, '-%s' % query_opt)))
- return _dict_from_tcltuple(self.tk.splitlist(
- self.tk.call(self._name, "map", style, *(_format_mapdict(kw)))))
+ return _splitdict(
+ self.tk,
+ self.tk.call(self._name, "map", style, *_format_mapdict(kw)),
+ conv=_tclobj_to_py)
def lookup(self, style, option, state=None, default=None):
@@ -1427,13 +1418,16 @@
def set(self, item, column=None, value=None):
- """With one argument, returns a dictionary of column/value pairs
- for the specified item. With two arguments, returns the current
- value of the specified column. With three arguments, sets the
+ """Query or set the value of given item.
+
+ With one argument, return a dictionary of column/value pairs
+ for the specified item. With two arguments, return the current
+ value of the specified column. With three arguments, set the
value of given column in given item to the specified value."""
res = self.tk.call(self._w, "set", item, column, value)
if column is None and value is None:
- return _dict_from_tcltuple(self.tk.splitlist(res), False)
+ return _splitdict(self.tk, res,
+ cut_minus=False, conv=_tclobj_to_py)
else:
return res
diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py
index c0afba6..d627034 100644
--- a/Lib/test/test_tcl.py
+++ b/Lib/test/test_tcl.py
@@ -7,6 +7,9 @@
# Skip this test if the _tkinter module wasn't built.
_tkinter = test_support.import_module('_tkinter')
+# Make sure tkinter._fix runs to set up the environment
+tkinter = test_support.import_fresh_module('Tkinter')
+
from Tkinter import Tcl
from _tkinter import TclError
@@ -565,6 +568,42 @@
for arg, res in testcases:
self.assertEqual(split(arg), res)
+ def test_splitdict(self):
+ splitdict = tkinter._splitdict
+ tcl = self.interp.tk
+
+ arg = '-a {1 2 3} -something foo status {}'
+ self.assertEqual(splitdict(tcl, arg, False),
+ {'-a': '1 2 3', '-something': 'foo', 'status': ''})
+ self.assertEqual(splitdict(tcl, arg),
+ {'a': '1 2 3', 'something': 'foo', 'status': ''})
+
+ arg = ('-a', (1, 2, 3), '-something', 'foo', 'status', '{}')
+ self.assertEqual(splitdict(tcl, arg, False),
+ {'-a': (1, 2, 3), '-something': 'foo', 'status': '{}'})
+ self.assertEqual(splitdict(tcl, arg),
+ {'a': (1, 2, 3), 'something': 'foo', 'status': '{}'})
+
+ self.assertRaises(RuntimeError, splitdict, tcl, '-a b -c ')
+ self.assertRaises(RuntimeError, splitdict, tcl, ('-a', 'b', '-c'))
+
+ arg = tcl.call('list',
+ '-a', (1, 2, 3), '-something', 'foo', 'status', ())
+ self.assertEqual(splitdict(tcl, arg),
+ {'a': (1, 2, 3) if self.wantobjects else '1 2 3',
+ 'something': 'foo', 'status': ''})
+
+ if tcl_version >= (8, 5):
+ arg = tcl.call('dict', 'create',
+ '-a', (1, 2, 3), '-something', 'foo', 'status', ())
+ if not self.wantobjects or get_tk_patchlevel() < (8, 5, 5):
+ # Before 8.5.5 dicts were converted to lists through string
+ expected = {'a': '1 2 3', 'something': 'foo', 'status': ''}
+ else:
+ expected = {'a': (1, 2, 3), 'something': 'foo', 'status': ''}
+ self.assertEqual(splitdict(tcl, arg), expected)
+
+
character_size = 4 if sys.maxunicode > 0xFFFF else 2
class BigmemTclTest(unittest.TestCase):
diff --git a/Misc/NEWS b/Misc/NEWS
index 91e1db4..2db7d5a 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -22,6 +22,9 @@
Library
-------
+- Issue #22226: First letter no longer is stripped from the "status" key in
+ the result of Treeview.heading().
+
- Issue #22051: turtledemo no longer reloads examples to re-run them.
Initialization of variables and gui setup should be done in main(),
which is called each time a demo is run, but not on import.