Skip to content

Commit 26aea45

Browse files
gh-97928: Partially restore the behavior of tkinter.Text.count() by default
By default, it preserves an inconsistent behavior of older Python versions: packs the count into a 1-tuple if only one or none options are specified (including 'update'), returns None instead of 0. Except that setting wantobjects to 0 no longer affects the result. Add a new parameter return_ints: specifying return_ints=True makes Text.count() always returning the single count as an integer insteaf of a 1-tuple or None.
1 parent 87cd20a commit 26aea45

File tree

5 files changed

+59
-28
lines changed

5 files changed

+59
-28
lines changed

Doc/whatsnew/3.13.rst

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,12 @@ tkinter
421421
:meth:`!tk_busy_current`, and :meth:`!tk_busy_status`.
422422
(Contributed by Miguel, klappnase and Serhiy Storchaka in :gh:`72684`.)
423423

424+
* Add new optional keyword parameter *return_ints* in
425+
the :meth:`Text.count <tkinter.Text.count>` method.
426+
Passing ``return_ints=True`` makes it always returning the single count
427+
as an integer instead of a 1-tuple or ``None``.
428+
(Contributed by Serhiy Storchaka in :gh:`97928`.)
429+
424430
* Add support of the "vsapi" element type in
425431
the :meth:`~tkinter.ttk.Style.element_create` method of
426432
:class:`tkinter.ttk.Style`.
@@ -1238,13 +1244,6 @@ that may require changes to your code.
12381244
Changes in the Python API
12391245
-------------------------
12401246

1241-
* :meth:`!tkinter.Text.count` now always returns an integer if one or less
1242-
counting options are specified.
1243-
Previously it could return a single count as a 1-tuple, an integer (only if
1244-
option ``"update"`` was specified) or ``None`` if no items found.
1245-
The result is now the same if ``wantobjects`` is set to ``0``.
1246-
(Contributed by Serhiy Storchaka in :gh:`97928`.)
1247-
12481247
* Functions :c:func:`PyDict_GetItem`, :c:func:`PyDict_GetItemString`,
12491248
:c:func:`PyMapping_HasKey`, :c:func:`PyMapping_HasKeyString`,
12501249
:c:func:`PyObject_HasAttr`, :c:func:`PyObject_HasAttrString`, and

Lib/idlelib/sidebar.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def get_displaylines(text, index):
2727
"""Display height, in lines, of a logical line in a Tk text widget."""
2828
return text.count(f"{index} linestart",
2929
f"{index} lineend",
30-
"displaylines")
30+
"displaylines", return_ints=True)
3131

3232
def get_widget_padding(widget):
3333
"""Get the total padding of a Tk widget, including its border."""

Lib/test/test_tkinter/test_text.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,27 +52,47 @@ def test_count(self):
5252
options = ('chars', 'indices', 'lines',
5353
'displaychars', 'displayindices', 'displaylines',
5454
'xpixels', 'ypixels')
55+
self.assertEqual(len(text.count('1.0', 'end', *options, return_ints=True)), 8)
5556
self.assertEqual(len(text.count('1.0', 'end', *options)), 8)
56-
self.assertEqual(text.count('1.0', 'end', 'chars', 'lines'), (124, 4))
57+
self.assertEqual(text.count('1.0', 'end', 'chars', 'lines', return_ints=True),
58+
(124, 4))
5759
self.assertEqual(text.count('1.3', '4.5', 'chars', 'lines'), (92, 3))
60+
self.assertEqual(text.count('4.5', '1.3', 'chars', 'lines', return_ints=True),
61+
(-92, -3))
5862
self.assertEqual(text.count('4.5', '1.3', 'chars', 'lines'), (-92, -3))
63+
self.assertEqual(text.count('1.3', '1.3', 'chars', 'lines', return_ints=True),
64+
(0, 0))
5965
self.assertEqual(text.count('1.3', '1.3', 'chars', 'lines'), (0, 0))
60-
self.assertEqual(text.count('1.0', 'end', 'lines'), 4)
61-
self.assertEqual(text.count('end', '1.0', 'lines'), -4)
62-
self.assertEqual(text.count('1.3', '1.5', 'lines'), 0)
63-
self.assertEqual(text.count('1.3', '1.3', 'lines'), 0)
64-
self.assertEqual(text.count('1.0', 'end'), 124) # 'indices' by default
65-
self.assertEqual(text.count('1.0', 'end', 'indices'), 124)
66+
self.assertEqual(text.count('1.0', 'end', 'lines', return_ints=True), 4)
67+
self.assertEqual(text.count('1.0', 'end', 'lines'), (4,))
68+
self.assertEqual(text.count('end', '1.0', 'lines', return_ints=True), -4)
69+
self.assertEqual(text.count('end', '1.0', 'lines'), (-4,))
70+
self.assertEqual(text.count('1.3', '1.5', 'lines', return_ints=True), 0)
71+
self.assertEqual(text.count('1.3', '1.5', 'lines'), None)
72+
self.assertEqual(text.count('1.3', '1.3', 'lines', return_ints=True), 0)
73+
self.assertEqual(text.count('1.3', '1.3', 'lines'), None)
74+
# Count 'indices' by default.
75+
self.assertEqual(text.count('1.0', 'end', return_ints=True), 124)
76+
self.assertEqual(text.count('1.0', 'end'), (124,))
77+
self.assertEqual(text.count('1.0', 'end', 'indices', return_ints=True), 124)
78+
self.assertEqual(text.count('1.0', 'end', 'indices'), (124,))
6679
self.assertRaises(tkinter.TclError, text.count, '1.0', 'end', 'spam')
6780
self.assertRaises(tkinter.TclError, text.count, '1.0', 'end', '-lines')
6881

69-
self.assertIsInstance(text.count('1.3', '1.5', 'ypixels'), int)
82+
self.assertIsInstance(text.count('1.3', '1.5', 'ypixels', return_ints=True), int)
83+
self.assertIsInstance(text.count('1.3', '1.5', 'ypixels'), tuple)
84+
self.assertIsInstance(text.count('1.3', '1.5', 'update', 'ypixels', return_ints=True), int)
7085
self.assertIsInstance(text.count('1.3', '1.5', 'update', 'ypixels'), int)
71-
self.assertEqual(text.count('1.3', '1.3', 'update', 'ypixels'), 0)
86+
self.assertEqual(text.count('1.3', '1.3', 'update', 'ypixels', return_ints=True), 0)
87+
self.assertEqual(text.count('1.3', '1.3', 'update', 'ypixels'), None)
88+
self.assertEqual(text.count('1.3', '1.5', 'update', 'indices', return_ints=True), 2)
7289
self.assertEqual(text.count('1.3', '1.5', 'update', 'indices'), 2)
73-
self.assertEqual(text.count('1.3', '1.3', 'update', 'indices'), 0)
74-
self.assertEqual(text.count('1.3', '1.5', 'update'), 2)
75-
self.assertEqual(text.count('1.3', '1.3', 'update'), 0)
90+
self.assertEqual(text.count('1.3', '1.3', 'update', 'indices', return_ints=True), 0)
91+
self.assertEqual(text.count('1.3', '1.3', 'update', 'indices'), None)
92+
self.assertEqual(text.count('1.3', '1.5', 'update', return_ints=True), 2)
93+
self.assertEqual(text.count('1.3', '1.5', 'update'), (2,))
94+
self.assertEqual(text.count('1.3', '1.3', 'update', return_ints=True), 0)
95+
self.assertEqual(text.count('1.3', '1.3', 'update'), None)
7696

7797

7898
if __name__ == "__main__":

Lib/tkinter/__init__.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3732,27 +3732,34 @@ def compare(self, index1, op, index2):
37323732
return self.tk.getboolean(self.tk.call(
37333733
self._w, 'compare', index1, op, index2))
37343734

3735-
def count(self, index1, index2, *options): # new in Tk 8.5
3735+
def count(self, index1, index2, *options, return_ints=False): # new in Tk 8.5
37363736
"""Counts the number of relevant things between the two indices.
37373737
37383738
If INDEX1 is after INDEX2, the result will be a negative number
37393739
(and this holds for each of the possible options).
37403740
37413741
The actual items which are counted depends on the options given.
37423742
The result is a tuple of integers, one for the result of each
3743-
counting option given, if more than one option is specified,
3744-
otherwise it is an integer. Valid counting options are "chars",
3745-
"displaychars", "displayindices", "displaylines", "indices",
3746-
"lines", "xpixels" and "ypixels". The default value, if no
3747-
option is specified, is "indices". There is an additional possible
3748-
option "update", which if given then all subsequent options ensure
3749-
that any possible out of date information is recalculated."""
3743+
counting option given, if more than one option is specified or
3744+
return_ints is false (default), otherwise it is an integer.
3745+
Valid counting options are "chars", "displaychars",
3746+
"displayindices", "displaylines", "indices", "lines", "xpixels"
3747+
and "ypixels". The default value, if no option is specified, is
3748+
"indices". There is an additional possible option "update",
3749+
which if given then all subsequent options ensure that any
3750+
possible out of date information is recalculated.
3751+
"""
37503752
options = ['-%s' % arg for arg in options]
37513753
res = self.tk.call(self._w, 'count', *options, index1, index2)
37523754
if not isinstance(res, int):
37533755
res = self._getints(res)
37543756
if len(res) == 1:
37553757
res, = res
3758+
if not return_ints:
3759+
if not res:
3760+
res = None
3761+
elif len(options) <= 1:
3762+
res = (res,)
37563763
return res
37573764

37583765
def debug(self, boolean=None):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Partially revert the behavior of :meth:`tkinter.Text.count`. By default it
2+
preserves the behavior of older Python versions, except that setting
3+
``wantobjects`` to 0 no longer has effect. Add a new parameter *return_ints*:
4+
specifying ``return_ints=True`` makes ``Text.count()`` always returning the
5+
single count as an integer instead of a 1-tuple or ``None``.

0 commit comments

Comments
 (0)