]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-140481: Improve error message when trying to iterate a Tk widget, image or font...
authorSerhiy Storchaka <storchaka@gmail.com>
Thu, 30 Oct 2025 11:11:56 +0000 (13:11 +0200)
committerGitHub <noreply@github.com>
Thu, 30 Oct 2025 11:11:56 +0000 (13:11 +0200)
Lib/test/test_tkinter/test_font.py
Lib/test/test_tkinter/test_images.py
Lib/test/test_tkinter/test_misc.py
Lib/tkinter/__init__.py
Lib/tkinter/font.py
Misc/NEWS.d/next/Library/2025-10-23-13-42-15.gh-issue-140481.XKxWpq.rst [new file with mode: 0644]

index 3616da54cf7075d8d1dfcc827e567b3aafe3cba0..fc50f9fdbb588c3ec15095611aab6ad6d611d7fc 100644 (file)
@@ -1,3 +1,4 @@
+import collections.abc
 import unittest
 import tkinter
 from tkinter import font
@@ -118,6 +119,16 @@ class FontTest(AbstractTkTest, unittest.TestCase):
             repr(self.font), f'<tkinter.font.Font object {fontname!r}>'
         )
 
+    def test_iterable_protocol(self):
+        self.assertNotIsSubclass(font.Font, collections.abc.Iterable)
+        self.assertNotIsSubclass(font.Font, collections.abc.Container)
+        self.assertNotIsInstance(self.font, collections.abc.Iterable)
+        self.assertNotIsInstance(self.font, collections.abc.Container)
+        with self.assertRaisesRegex(TypeError, 'is not iterable'):
+            iter(self.font)
+        with self.assertRaisesRegex(TypeError, 'is not a container or iterable'):
+            self.font in self.font
+
 
 class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
 
index 38371fe00d6eb5bc1cfe82653f912d803296c05b..358a18beee25710f0478f50e23cdde4deab95d82 100644 (file)
@@ -1,3 +1,4 @@
+import collections.abc
 import unittest
 import tkinter
 from test import support
@@ -61,7 +62,33 @@ class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
         self.assertRaises(RuntimeError, tkinter.PhotoImage)
 
 
-class BitmapImageTest(AbstractTkTest, unittest.TestCase):
+class BaseImageTest:
+    def create(self):
+        return self.image_class('::img::test', master=self.root,
+                                file=self.testfile)
+
+    def test_bug_100814(self):
+        # gh-100814: Passing a callable option value causes AttributeError.
+        with self.assertRaises(tkinter.TclError):
+            self.image_class('::img::test', master=self.root, spam=print)
+        image = self.image_class('::img::test', master=self.root)
+        with self.assertRaises(tkinter.TclError):
+            image.configure(spam=print)
+
+    def test_iterable_protocol(self):
+        image = self.create()
+        self.assertNotIsSubclass(self.image_class, collections.abc.Iterable)
+        self.assertNotIsSubclass(self.image_class, collections.abc.Container)
+        self.assertNotIsInstance(image, collections.abc.Iterable)
+        self.assertNotIsInstance(image, collections.abc.Container)
+        with self.assertRaisesRegex(TypeError, 'is not iterable'):
+            iter(image)
+        with self.assertRaisesRegex(TypeError, 'is not a container or iterable'):
+            image in image
+
+
+class BitmapImageTest(BaseImageTest, AbstractTkTest, unittest.TestCase):
+    image_class = tkinter.BitmapImage
 
     @classmethod
     def setUpClass(cls):
@@ -144,26 +171,15 @@ class BitmapImageTest(AbstractTkTest, unittest.TestCase):
         self.assertEqual(image['foreground'],
                          '-foreground {} {} #000000 yellow')
 
-    def test_bug_100814(self):
-        # gh-100814: Passing a callable option value causes AttributeError.
-        with self.assertRaises(tkinter.TclError):
-            tkinter.BitmapImage('::img::test', master=self.root, spam=print)
-        image = tkinter.BitmapImage('::img::test', master=self.root)
-        with self.assertRaises(tkinter.TclError):
-            image.configure(spam=print)
-
 
-class PhotoImageTest(AbstractTkTest, unittest.TestCase):
+class PhotoImageTest(BaseImageTest, AbstractTkTest, unittest.TestCase):
+    image_class = tkinter.PhotoImage
 
     @classmethod
     def setUpClass(cls):
         AbstractTkTest.setUpClass.__func__(cls)
         cls.testfile = support.findfile('python.gif', subdir='tkinterdata')
 
-    def create(self):
-        return tkinter.PhotoImage('::img::test', master=self.root,
-                                  file=self.testfile)
-
     def colorlist(self, *args):
         if tkinter.TkVersion >= 8.6 and self.wantobjects:
             return args
@@ -282,14 +298,6 @@ class PhotoImageTest(AbstractTkTest, unittest.TestCase):
         image.configure(palette='3/4/2')
         self.assertEqual(image['palette'], '3/4/2')
 
-    def test_bug_100814(self):
-        # gh-100814: Passing a callable option value causes AttributeError.
-        with self.assertRaises(tkinter.TclError):
-            tkinter.PhotoImage('::img::test', master=self.root, spam=print)
-        image = tkinter.PhotoImage('::img::test', master=self.root)
-        with self.assertRaises(tkinter.TclError):
-            image.configure(spam=print)
-
     def test_blank(self):
         image = self.create()
         image.blank()
index 0c76e07066f8a87fd8295a2296fa37b760821d98..32e2329506e7ff732d8c8ff28655c2652f25a073 100644 (file)
@@ -1,3 +1,4 @@
+import collections.abc
 import functools
 import unittest
 import tkinter
@@ -508,6 +509,17 @@ class MiscTest(AbstractTkTest, unittest.TestCase):
         widget.selection_range(0, 'end')
         self.assertEqual(widget.selection_get(), '\u20ac\0abc\x00def')
 
+    def test_iterable_protocol(self):
+        widget = tkinter.Entry(self.root)
+        self.assertNotIsSubclass(tkinter.Entry, collections.abc.Iterable)
+        self.assertNotIsSubclass(tkinter.Entry, collections.abc.Container)
+        self.assertNotIsInstance(widget, collections.abc.Iterable)
+        self.assertNotIsInstance(widget, collections.abc.Container)
+        with self.assertRaisesRegex(TypeError, 'is not iterable'):
+            iter(widget)
+        with self.assertRaisesRegex(TypeError, 'is not a container or iterable'):
+            widget in widget
+
 
 class WmTest(AbstractTkTest, unittest.TestCase):
 
index 9526d8b949fa3b8d76ad8db250d9461daf3352d4..c54530740395f75fccb29cf4449775b32bf42016 100644 (file)
@@ -1848,6 +1848,7 @@ class Misc:
         return self.tk.call(self._w, 'cget', '-' + key)
 
     __getitem__ = cget
+    __iter__ = None  # prevent using __getitem__ for iteration
 
     def __setitem__(self, key, value):
         self.configure({key: value})
@@ -4280,6 +4281,8 @@ class Image:
     def __getitem__(self, key):
         return self.tk.call(self.name, 'configure', '-'+key)
 
+    __iter__ = None  # prevent using __getitem__ for iteration
+
     def configure(self, **kw):
         """Configure the image."""
         res = ()
index 7aed523cce3784b9a2fd72bceebe9d0adeb8730c..896e910d69f6f3308caca1ca0cc20689fde1c9cc 100644 (file)
@@ -114,6 +114,8 @@ class Font:
     def __setitem__(self, key, value):
         self.configure(**{key: value})
 
+    __iter__ = None  # prevent using __getitem__ for iteration
+
     def __del__(self):
         try:
             if self.delete_font:
diff --git a/Misc/NEWS.d/next/Library/2025-10-23-13-42-15.gh-issue-140481.XKxWpq.rst b/Misc/NEWS.d/next/Library/2025-10-23-13-42-15.gh-issue-140481.XKxWpq.rst
new file mode 100644 (file)
index 0000000..1f511c3
--- /dev/null
@@ -0,0 +1 @@
+Improve error message when trying to iterate a Tk widget, image or font.