https://github.com/python/cpython/commit/62cbd34df74e4e07bb50edcaeec7454e71da47ce
commit: 62cbd34df74e4e07bb50edcaeec7454e71da47ce
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-09-13T11:03:08+03:00
summary:

gh-156961: Fix tkinter.font.Font for a font name returned as a Tcl object 
(GH-157028)

Tk can return a font name or description as a Tcl object, for example
from ttk.Style().lookup("TButton", "font"), Menu.entrycget("font"),
ttk.Entry.cget("font"), or the default value in the result of
configure().  Such an object does not compare equal to a string, so it
was not recognized as the name of an existing named font.  Keep it as
is, so that it is passed back to Tk, and only convert it where it is
compared with a string.

files:
A Misc/NEWS.d/next/Library/2026-09-05-22-39-22.gh-issue-156961.ZtYqoA.rst
M Lib/test/test_tkinter/test_font.py
M Lib/tkinter/font.py

diff --git a/Lib/test/test_tkinter/test_font.py 
b/Lib/test/test_tkinter/test_font.py
index 3d76ae630d97e38..16c9c1dc22bbca7 100644
--- a/Lib/test/test_tkinter/test_font.py
+++ b/Lib/test/test_tkinter/test_font.py
@@ -24,6 +24,16 @@ def actual_size(self, desc):
         # The requested size is not always available (e.g. bitmap fonts).
         return self.root.tk.call('font', 'actual', desc, '-size')
 
+    def tcl_font_object(self, desc):
+        # Return a font name or description as a Tcl object representing a
+        # font, as Tk returns for example from ttk.Style().lookup().
+        tk = self.root.tk
+        tk.call('set', '_font', desc)
+        tk.eval('font measure $_font x')  # convert the Tcl object to a font
+        obj = tk.call('set', '_font')
+        tk.call('unset', '_font')
+        return obj
+
     def test_configure(self):
         self.assertEqual(self.font.config, self.font.configure)
         options = self.font.configure()
@@ -150,6 +160,36 @@ def test_existing(self):
         # A name or a description is required.
         self.assertRaises(TypeError, font.Font, root=self.root, exists=True)
 
+    def test_tcl_object(self):
+        # Tk can return a font as a Tcl object (gh-156961).
+        if not self.wantobjects:
+            self.skipTest('Tcl objects are converted to strings')
+        obj = self.tcl_font_object(fontname)
+        self.assertEqual(obj.typename, 'font')
+
+        # It can be used as the name of an existing named font.
+        for f in (font.Font(root=self.root, name=obj, exists=True),
+                  font.nametofont(obj, root=self.root)):
+            # The Tcl object is kept as is, so that it is passed back to Tk.
+            self.assertIs(f.name, obj)
+            self.assertEqual(str(f), fontname)
+            self.assertEqual(f.actual(), self.font.actual())
+            self.assertEqual(f, self.font)
+            self.assertEqual(self.font, f)
+        # Referring to a non-existent named font still fails.
+        self.assertRaisesRegex(tkinter.TclError, 'named font nosuchfont',
+                               font.Font, root=self.root, exists=True,
+                               name=self.tcl_font_object('nosuchfont'))
+
+        # It can also be wrapped as a font description.
+        obj = self.tcl_font_object(('Times', 20, 'bold'))
+        f = font.Font(root=self.root, font=obj, exists=True)
+        self.assertIs(f.name, obj)
+        self.assertEqual(str(f), 'Times 20 bold')
+        self.assertNotIn(f.name, font.names(self.root))
+        self.assertEqual(f.actual('weight'), 'bold')
+        self.assertEqual(f.actual('size'), self.actual_size(('Times', 20, 
'bold')))
+
     def test_copy(self):
         # size=-20 (pixels): copy() copies the configured options, so the
         # size is preserved rather than resolved (gh-143990).
diff --git a/Lib/tkinter/font.py b/Lib/tkinter/font.py
index 5b663a4e456456b..1349e49fbd68a5b 100644
--- a/Lib/tkinter/font.py
+++ b/Lib/tkinter/font.py
@@ -104,7 +104,8 @@ def __init__(self, root=None, font=None, name=None, 
exists=False,
             if exists:
                 self.name = name
                 # confirm font exists
-                if self.name not in tk.splitlist(tk.call("font", "names")):
+                name = getattr(name, 'string', name)  # can be a Tcl object
+                if name not in tk.splitlist(tk.call("font", "names")):
                     raise tkinter._tkinter.TclError(
                         "named font %s does not already exist" % (self.name,))
                 # if font config info supplied, apply it
@@ -123,11 +124,11 @@ def __init__(self, root=None, font=None, name=None, 
exists=False,
         self._call  = tk.call
 
     def __str__(self):
-        # A wrapped description is a list or tuple, not a string; format it as
-        # a Tcl word so it can be used as an option value (as ttk does).
-        if isinstance(self.name, str):
-            return self.name
-        return tkinter._join(self.name)
+        # A wrapped description can be a list or tuple; format it as a Tcl
+        # word so it can be used as an option value (as ttk does).
+        if isinstance(self.name, (list, tuple)):
+            return tkinter._join(self.name)
+        return str(self.name)
 
     def __repr__(self):
         return f"<{self.__class__.__module__}.{self.__class__.__qualname__}" \
@@ -136,7 +137,13 @@ def __repr__(self):
     def __eq__(self, other):
         if not isinstance(other, Font):
             return NotImplemented
-        return self.name == other.name and self._tk == other._tk
+        name = self.name
+        other_name = other.name
+        if type(name) is not type(other_name):
+            # A Tcl object does not compare equal to a string.
+            name = getattr(name, 'string', name)
+            other_name = getattr(other_name, 'string', other_name)
+        return name == other_name and self._tk == other._tk
 
     def __getitem__(self, key):
         return self.cget(key)
diff --git 
a/Misc/NEWS.d/next/Library/2026-09-05-22-39-22.gh-issue-156961.ZtYqoA.rst 
b/Misc/NEWS.d/next/Library/2026-09-05-22-39-22.gh-issue-156961.ZtYqoA.rst
new file mode 100644
index 000000000000000..9df2868c30f16d1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-05-22-39-22.gh-issue-156961.ZtYqoA.rst
@@ -0,0 +1,3 @@
+Fix :func:`tkinter.font.nametofont` and the :class:`tkinter.font.Font`
+constructor for a font name or description returned by Tk as a Tcl object,
+for example by :meth:`ttk.Style.lookup() <tkinter.ttk.Style.lookup>`.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to