https://github.com/python/cpython/commit/872eafd2b05731d3c4a1f33249dffbb5c2e53ce3
commit: 872eafd2b05731d3c4a1f33249dffbb5c2e53ce3
branch: main
author: Hugo van Kemenade <[email protected]>
committer: hugovk <[email protected]>
date: 2025-09-29T12:03:23+03:00
summary:

gh-76007: Deprecate `__version__` attribute (#138675)

Co-authored-by: AN Long <[email protected]>
Co-authored-by: Petr Viktorin <[email protected]>

files:
A Doc/deprecations/pending-removal-in-3.20.rst
A Misc/NEWS.d/next/Library/2025-09-08-17-32-02.gh-issue-76007.peEgcr.rst
M Doc/deprecations/index.rst
M Doc/whatsnew/3.12.rst
M Doc/whatsnew/3.13.rst
M Doc/whatsnew/3.14.rst
M Doc/whatsnew/3.15.rst
M Lib/argparse.py
M Lib/csv.py
M Lib/ctypes/macholib/__init__.py
M Lib/ipaddress.py
M Lib/json/__init__.py
M Lib/logging/__init__.py
M Lib/optparse.py
M Lib/pickle.py
M Lib/platform.py
M Lib/re/__init__.py
M Lib/socketserver.py
M Lib/tabnanny.py
M Lib/test/test_argparse.py
M Lib/test/test_csv.py
M Lib/test/test_ctypes/test_macholib.py
M Lib/test/test_ipaddress.py
M Lib/test/test_json/__init__.py
M Lib/test/test_logging.py
M Lib/test/test_optparse.py
M Lib/test/test_re.py
M Lib/test/test_socketserver.py
M Lib/test/test_tabnanny.py
M Lib/test/test_tkinter/test_font.py
M Lib/test/test_ttk/__init__.py
M Lib/tkinter/font.py
M Lib/tkinter/ttk.py
M Misc/sbom.spdx.json

diff --git a/Doc/deprecations/index.rst b/Doc/deprecations/index.rst
index c6e05c176b2aa1..f3cecb321d633f 100644
--- a/Doc/deprecations/index.rst
+++ b/Doc/deprecations/index.rst
@@ -9,6 +9,8 @@ Deprecations
 
 .. include:: pending-removal-in-3.19.rst
 
+.. include:: pending-removal-in-3.20.rst
+
 .. include:: pending-removal-in-future.rst
 
 C API deprecations
diff --git a/Doc/deprecations/pending-removal-in-3.20.rst 
b/Doc/deprecations/pending-removal-in-3.20.rst
new file mode 100644
index 00000000000000..8bc863b185d921
--- /dev/null
+++ b/Doc/deprecations/pending-removal-in-3.20.rst
@@ -0,0 +1,23 @@
+Pending removal in Python 3.20
+------------------------------
+
+* The ``__version__`` attribute has been deprecated in these standard library
+  modules and will be removed in Python 3.20.
+  Use :py:data:`sys.version_info` instead.
+
+  - :mod:`argparse`
+  - :mod:`csv`
+  - :mod:`!ctypes.macholib`
+  - :mod:`ipaddress`
+  - :mod:`json`
+  - :mod:`logging` (``__date__`` also deprecated)
+  - :mod:`optparse`
+  - :mod:`pickle`
+  - :mod:`platform`
+  - :mod:`re`
+  - :mod:`socketserver`
+  - :mod:`tabnanny`
+  - :mod:`tkinter.font`
+  - :mod:`tkinter.ttk`
+
+  (Contributed by Hugo van Kemenade in :gh:`76007`.)
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index 2b6939cd323dcf..8badfe9a6b49b9 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -1363,6 +1363,10 @@ Deprecated
 
 .. include:: ../deprecations/pending-removal-in-3.17.rst
 
+.. include:: ../deprecations/pending-removal-in-3.19.rst
+
+.. include:: ../deprecations/pending-removal-in-3.20.rst
+
 .. include:: ../deprecations/pending-removal-in-future.rst
 
 .. _whatsnew312-removed:
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index fbb27adbf9969c..bc5a21e172d0bc 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -2026,6 +2026,10 @@ New Deprecations
 
 .. include:: ../deprecations/pending-removal-in-3.17.rst
 
+.. include:: ../deprecations/pending-removal-in-3.19.rst
+
+.. include:: ../deprecations/pending-removal-in-3.20.rst
+
 .. include:: ../deprecations/pending-removal-in-future.rst
 
 CPython Bytecode Changes
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index caeff3a00aa049..102d8ee9830a92 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -2794,6 +2794,8 @@ Deprecated
 
 .. include:: ../deprecations/pending-removal-in-3.19.rst
 
+.. include:: ../deprecations/pending-removal-in-3.20.rst
+
 .. include:: ../deprecations/pending-removal-in-future.rst
 
 
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index d8c5d9378008eb..4b176d6c8e6034 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -631,9 +631,42 @@ hashlib
 
   (Contributed by Bénédikt Tran in :gh:`134978`.)
 
+__version__
+-----------
+
+* The ``__version__`` attribute has been deprecated in these standard library
+  modules and will be removed in Python 3.20.
+  Use :py:data:`sys.version_info` instead.
+
+  - :mod:`argparse`
+  - :mod:`csv`
+  - :mod:`!ctypes.macholib`
+  - :mod:`ipaddress`
+  - :mod:`json`
+  - :mod:`logging` (``__date__`` also deprecated)
+  - :mod:`optparse`
+  - :mod:`pickle`
+  - :mod:`platform`
+  - :mod:`re`
+  - :mod:`socketserver`
+  - :mod:`tabnanny`
+  - :mod:`tkinter.font`
+  - :mod:`tkinter.ttk`
+
+  (Contributed by Hugo van Kemenade in :gh:`76007`.)
 
 .. Add deprecations above alphabetically, not here at the end.
 
+.. include:: ../deprecations/pending-removal-in-3.16.rst
+
+.. include:: ../deprecations/pending-removal-in-3.17.rst
+
+.. include:: ../deprecations/pending-removal-in-3.19.rst
+
+.. include:: ../deprecations/pending-removal-in-3.20.rst
+
+.. include:: ../deprecations/pending-removal-in-future.rst
+
 Removed
 =======
 
diff --git a/Lib/argparse.py b/Lib/argparse.py
index 2144c81886ad19..863e951528b6d0 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -64,7 +64,6 @@
 still considered an implementation detail.)
 """
 
-__version__ = '1.1'
 __all__ = [
     'ArgumentParser',
     'ArgumentError',
@@ -2773,3 +2772,12 @@ def error(self, message):
     def _warning(self, message):
         args = {'prog': self.prog, 'message': message}
         self._print_message(_('%(prog)s: warning: %(message)s\n') % args, 
_sys.stderr)
+
+
+def __getattr__(name):
+    if name == "__version__":
+        from warnings import _deprecated
+
+        _deprecated("__version__", remove=(3, 20))
+        return "1.1"  # Do not change
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/csv.py b/Lib/csv.py
index 0a627ba7a512fa..98eab01429a8ec 100644
--- a/Lib/csv.py
+++ b/Lib/csv.py
@@ -81,8 +81,6 @@ class excel:
            "unregister_dialect", "DictReader", "DictWriter",
            "unix_dialect"]
 
-__version__ = "1.0"
-
 
 class Dialect:
     """Describe a CSV dialect.
@@ -511,3 +509,12 @@ def has_header(self, sample):
                     hasHeader -= 1
 
         return hasHeader > 0
+
+
+def __getattr__(name):
+    if name == "__version__":
+        from warnings import _deprecated
+
+        _deprecated("__version__", remove=(3, 20))
+        return "1.0"  # Do not change
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/ctypes/macholib/__init__.py b/Lib/ctypes/macholib/__init__.py
index 5621defccd61d1..f00e55f8131c9d 100644
--- a/Lib/ctypes/macholib/__init__.py
+++ b/Lib/ctypes/macholib/__init__.py
@@ -6,4 +6,10 @@
 And also Apple's documentation.
 """
 
-__version__ = '1.0'
+def __getattr__(name):
+    if name == "__version__":
+        from warnings import _deprecated
+
+        _deprecated("__version__", remove=(3, 20))
+        return "1.0"  # Do not change
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py
index 7470dc6c52d3a8..aa0cf4a0620cd0 100644
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -8,9 +8,6 @@
 
 """
 
-__version__ = '1.0'
-
-
 import functools
 
 IPV4LENGTH = 32
@@ -2419,3 +2416,12 @@ class _IPv6Constants:
 
 IPv6Address._constants = _IPv6Constants
 IPv6Network._constants = _IPv6Constants
+
+
+def __getattr__(name):
+    if name == "__version__":
+        from warnings import _deprecated
+
+        _deprecated("__version__", remove=(3, 20))
+        return "1.0"  # Do not change
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/json/__init__.py b/Lib/json/__init__.py
index 1d972d22ded072..c8fdd0d99a0efc 100644
--- a/Lib/json/__init__.py
+++ b/Lib/json/__init__.py
@@ -95,7 +95,6 @@
     $ echo '{ 1.2:3.4}' | python -m json
     Expecting property name enclosed in double quotes: line 1 column 3 (char 2)
 """
-__version__ = '2.0.9'
 __all__ = [
     'dump', 'dumps', 'load', 'loads',
     'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
@@ -357,3 +356,12 @@ def loads(s, *, cls=None, object_hook=None, 
parse_float=None,
     if parse_constant is not None:
         kw['parse_constant'] = parse_constant
     return cls(**kw).decode(s)
+
+
+def __getattr__(name):
+    if name == "__version__":
+        from warnings import _deprecated
+
+        _deprecated("__version__", remove=(3, 20))
+        return "2.0.9"  # Do not change
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py
index c5860d53b1bdff..431ff41b352048 100644
--- a/Lib/logging/__init__.py
+++ b/Lib/logging/__init__.py
@@ -45,9 +45,6 @@
 
 __author__  = "Vinay Sajip <[email protected]>"
 __status__  = "production"
-# The following module attributes are no longer updated.
-__version__ = "0.5.1.2"
-__date__    = "07 February 2010"
 
 #---------------------------------------------------------------------------
 #   Miscellaneous module data
@@ -2341,3 +2338,16 @@ def captureWarnings(capture):
         if _warnings_showwarning is not None:
             warnings.showwarning = _warnings_showwarning
             _warnings_showwarning = None
+
+
+def __getattr__(name):
+    if name in ("__version__", "__date__"):
+        from warnings import _deprecated
+
+        _deprecated(name, remove=(3, 20))
+        return {  # Do not change
+            "__version__": "0.5.1.2",
+            "__date__": "07 February 2010",
+        }[name]
+
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/optparse.py b/Lib/optparse.py
index 38cf16d21efffa..02ff7140882ed6 100644
--- a/Lib/optparse.py
+++ b/Lib/optparse.py
@@ -21,8 +21,6 @@
    (options, args) = parser.parse_args()
 """
 
-__version__ = "1.5.3"
-
 __all__ = ['Option',
            'make_option',
            'SUPPRESS_HELP',
@@ -1669,3 +1667,12 @@ def _match_abbrev(s, wordmap):
 # which will become a factory function when there are many Option
 # classes.
 make_option = Option
+
+
+def __getattr__(name):
+    if name == "__version__":
+        from warnings import _deprecated
+
+        _deprecated("__version__", remove=(3, 20))
+        return "1.5.3"  # Do not change
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/pickle.py b/Lib/pickle.py
index beaefae0479d3c..729c215514ad24 100644
--- a/Lib/pickle.py
+++ b/Lib/pickle.py
@@ -17,7 +17,6 @@
 
 Misc variables:
 
-    __version__
     format_version
     compatible_formats
 
diff --git a/Lib/platform.py b/Lib/platform.py
index 4028012dc3ce6a..4db93bea2a39e1 100644
--- a/Lib/platform.py
+++ b/Lib/platform.py
@@ -110,8 +110,6 @@
 
 """
 
-__version__ = '1.1.0'
-
 import collections
 import os
 import re
@@ -1436,5 +1434,14 @@ def _main(args: list[str] | None = None):
     print(platform(aliased, terse))
 
 
+def __getattr__(name):
+    if name == "__version__":
+        from warnings import _deprecated
+
+        _deprecated("__version__", remove=(3, 20))
+        return "1.1.0"  # Do not change
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+
+
 if __name__ == "__main__":
     _main()
diff --git a/Lib/re/__init__.py b/Lib/re/__init__.py
index af2808a77da691..a5316391297f4c 100644
--- a/Lib/re/__init__.py
+++ b/Lib/re/__init__.py
@@ -137,8 +137,6 @@
     "UNICODE", "NOFLAG", "RegexFlag", "PatternError"
 ]
 
-__version__ = "2.2.1"
-
 @enum.global_enum
 @enum._simple_enum(enum.IntFlag, boundary=enum.KEEP)
 class RegexFlag:
@@ -426,3 +424,12 @@ def scan(self, string):
                 append(action)
             i = j
         return result, string[i:]
+
+
+def __getattr__(name):
+    if name == "__version__":
+        from warnings import _deprecated
+
+        _deprecated("__version__", remove=(3, 20))
+        return "2.2.1"  # Do not change
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/socketserver.py b/Lib/socketserver.py
index 93b0a23be27f68..ec389457ef5b1c 100644
--- a/Lib/socketserver.py
+++ b/Lib/socketserver.py
@@ -120,9 +120,6 @@ class will essentially render the service "deaf" while one 
request is
 
 # Author of the BaseServer patch: Luke Kenneth Casson Leighton
 
-__version__ = "0.4"
-
-
 import socket
 import selectors
 import os
@@ -861,3 +858,12 @@ def setup(self):
 
     def finish(self):
         self.socket.sendto(self.wfile.getvalue(), self.client_address)
+
+
+def __getattr__(name):
+    if name == "__version__":
+        from warnings import _deprecated
+
+        _deprecated("__version__", remove=(3, 20))
+        return "0.4"  # Do not change
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/Lib/tabnanny.py b/Lib/tabnanny.py
index c0097351b269f2..272e8e33e0c46a 100644
--- a/Lib/tabnanny.py
+++ b/Lib/tabnanny.py
@@ -16,8 +16,6 @@
 # XXX The API needs to undergo changes however; the current code is too
 # XXX script-like.  This will be addressed later.
 
-__version__ = "6"
-
 import os
 import sys
 import tokenize
@@ -334,5 +332,14 @@ def _process_tokens(tokens):
                 raise NannyNag(start[0], msg, line)
 
 
+def __getattr__(name):
+    if name == "__version__":
+        from warnings import _deprecated
+
+        _deprecated("__version__", remove=(3, 20))
+        return "6"  # Do not change
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+
+
 if __name__ == '__main__':
     main()
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index fc73174d98cd6f..90bfcd0ae3e29b 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -7356,6 +7356,16 @@ def __init__(self, prog):
         '''))
 
 
+class TestModule(unittest.TestCase):
+    def test_deprecated__version__(self):
+        with self.assertWarnsRegex(
+            DeprecationWarning,
+            "'__version__' is deprecated and slated for removal in Python 
3.20",
+        ) as cm:
+            getattr(argparse, "__version__")
+        self.assertEqual(cm.filename, __file__)
+
+
 def tearDownModule():
     # Remove global references to avoid looking like we have refleaks.
     RFile.seen = {}
diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py
index 60feab225a107c..50431b562f90ba 100644
--- a/Lib/test/test_csv.py
+++ b/Lib/test/test_csv.py
@@ -1603,5 +1603,16 @@ def test_disallow_instantiation(self):
             with self.subTest(tp=tp):
                 check_disallow_instantiation(self, tp)
 
+
+class TestModule(unittest.TestCase):
+    def test_deprecated__version__(self):
+        with self.assertWarnsRegex(
+            DeprecationWarning,
+            "'__version__' is deprecated and slated for removal in Python 
3.20",
+        ) as cm:
+            getattr(csv, "__version__")
+        self.assertEqual(cm.filename, __file__)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/Lib/test/test_ctypes/test_macholib.py 
b/Lib/test/test_ctypes/test_macholib.py
index 9d90617995623d..9a5e18ed589d44 100644
--- a/Lib/test/test_ctypes/test_macholib.py
+++ b/Lib/test/test_ctypes/test_macholib.py
@@ -108,5 +108,17 @@ def test_framework_info(self):
                          d('P', 'F.framework/Versions/A/F_debug', 'F', 'A', 
'debug'))
 
 
+class TestModule(unittest.TestCase):
+    def test_deprecated__version__(self):
+        import ctypes.macholib
+
+        with self.assertWarnsRegex(
+            DeprecationWarning,
+            "'__version__' is deprecated and slated for removal in Python 
3.20",
+        ) as cm:
+            getattr(ctypes.macholib, "__version__")
+        self.assertEqual(cm.filename, __file__)
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py
index 62929f9dd68b5a..11721a59972672 100644
--- a/Lib/test/test_ipaddress.py
+++ b/Lib/test/test_ipaddress.py
@@ -2821,5 +2821,15 @@ def testNetworkV6HashCollisions(self):
         )
 
 
+class TestModule(unittest.TestCase):
+    def test_deprecated__version__(self):
+        with self.assertWarnsRegex(
+            DeprecationWarning,
+            "'__version__' is deprecated and slated for removal in Python 
3.20",
+        ) as cm:
+            getattr(ipaddress, "__version__")
+        self.assertEqual(cm.filename, __file__)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/Lib/test/test_json/__init__.py b/Lib/test/test_json/__init__.py
index 74b64ed86a3183..e58fbee41af3e6 100644
--- a/Lib/test/test_json/__init__.py
+++ b/Lib/test/test_json/__init__.py
@@ -47,6 +47,16 @@ def test_cjson(self):
                          '_json')
 
 
+class TestModule(unittest.TestCase):
+    def test_deprecated__version__(self):
+        with self.assertWarnsRegex(
+            DeprecationWarning,
+            "'__version__' is deprecated and slated for removal in Python 
3.20",
+        ) as cm:
+            getattr(json, "__version__")
+        self.assertEqual(cm.filename, __file__)
+
+
 def load_tests(loader, _, pattern):
     suite = unittest.TestSuite()
     for mod in (json, json.encoder, json.decoder):
diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
index 7a08f9ec9de9fb..1f7a4d9e197f9c 100644
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -7236,6 +7236,16 @@ def test__all__(self):
         support.check__all__(self, logging, not_exported=not_exported)
 
 
+class TestModule(unittest.TestCase):
+    def test_deprecated__version__and__date__(self):
+        msg = "is deprecated and slated for removal in Python 3.20"
+        for attr in ("__version__", "__date__"):
+            with self.subTest(attr=attr):
+                with self.assertWarnsRegex(DeprecationWarning, msg) as cm:
+                    getattr(logging, attr)
+                self.assertEqual(cm.filename, __file__)
+
+
 # Set the locale to the platform-dependent default.  I have no idea
 # why the test does this, but in any case we save the current locale
 # first and restore it at the end.
diff --git a/Lib/test/test_optparse.py b/Lib/test/test_optparse.py
index e476e4727803e5..fc8ef9520b3c0f 100644
--- a/Lib/test/test_optparse.py
+++ b/Lib/test/test_optparse.py
@@ -1666,6 +1666,16 @@ def test_translations(self):
         self.assertMsgidsEqual(optparse)
 
 
+class TestModule(unittest.TestCase):
+    def test_deprecated__version__(self):
+        with self.assertWarnsRegex(
+            DeprecationWarning,
+            "'__version__' is deprecated and slated for removal in Python 
3.20",
+        ) as cm:
+            getattr(optparse, "__version__")
+        self.assertEqual(cm.filename, __file__)
+
+
 if __name__ == '__main__':
     # To regenerate translation snapshots
     if len(sys.argv) > 1 and sys.argv[1] == '--snapshot-update':
diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py
index d4a2863728e1f8..5fc95087f2b6ad 100644
--- a/Lib/test/test_re.py
+++ b/Lib/test/test_re.py
@@ -3116,5 +3116,15 @@ def test_re_tests(self):
                     self.assertTrue(obj.search(s))
 
 
+class TestModule(unittest.TestCase):
+    def test_deprecated__version__(self):
+        with self.assertWarnsRegex(
+            DeprecationWarning,
+            "'__version__' is deprecated and slated for removal in Python 
3.20",
+        ) as cm:
+            getattr(re, "__version__")
+        self.assertEqual(cm.filename, __file__)
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py
index 893372cbbd0be9..a5e03020e5bd57 100644
--- a/Lib/test/test_socketserver.py
+++ b/Lib/test/test_socketserver.py
@@ -511,5 +511,15 @@ class MyServer(socketserver.ThreadingMixIn, 
socketserver.TCPServer):
         server.server_close()
 
 
+class TestModule(unittest.TestCase):
+    def test_deprecated__version__(self):
+        with self.assertWarnsRegex(
+            DeprecationWarning,
+            "'__version__' is deprecated and slated for removal in Python 
3.20",
+        ) as cm:
+            getattr(socketserver, "__version__")
+        self.assertEqual(cm.filename, __file__)
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Lib/test/test_tabnanny.py b/Lib/test/test_tabnanny.py
index 30dcb3e3c4f4f9..e575aac037e917 100644
--- a/Lib/test/test_tabnanny.py
+++ b/Lib/test/test_tabnanny.py
@@ -3,7 +3,8 @@
 Glossary:
     * errored    : Whitespace related problems present in file.
 """
-from unittest import TestCase, mock
+
+from unittest import TestCase, main, mock
 import errno
 import os
 import tabnanny
@@ -352,3 +353,17 @@ def test_double_verbose_mode(self):
                 "offending line: '\\tprint(\"world\")'"
             ).strip()
             self.validate_cmd("-vv", path, stdout=stdout, partial=True)
+
+
+class TestModule(TestCase):
+    def test_deprecated__version__(self):
+        with self.assertWarnsRegex(
+            DeprecationWarning,
+            "'__version__' is deprecated and slated for removal in Python 
3.20",
+        ) as cm:
+            getattr(tabnanny, "__version__")
+        self.assertEqual(cm.filename, __file__)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/Lib/test/test_tkinter/test_font.py 
b/Lib/test/test_tkinter/test_font.py
index 563707ddd2fa9b..3616da54cf7075 100644
--- a/Lib/test/test_tkinter/test_font.py
+++ b/Lib/test/test_tkinter/test_font.py
@@ -159,5 +159,15 @@ def test_nametofont(self):
         self.assertRaises(RuntimeError, font.nametofont, fontname)
 
 
+class TestModule(unittest.TestCase):
+    def test_deprecated__version__(self):
+        with self.assertWarnsRegex(
+            DeprecationWarning,
+            "'__version__' is deprecated and slated for removal in Python 
3.20",
+        ) as cm:
+            getattr(font, "__version__")
+        self.assertEqual(cm.filename, __file__)
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Lib/test/test_ttk/__init__.py b/Lib/test/test_ttk/__init__.py
index 7ee7ffbd6d7408..4a077a0f966fbb 100644
--- a/Lib/test/test_ttk/__init__.py
+++ b/Lib/test/test_ttk/__init__.py
@@ -19,6 +19,16 @@
 from tkinter import ttk
 
 
+class TestModule(unittest.TestCase):
+    def test_deprecated__version__(self):
+        with self.assertWarnsRegex(
+            DeprecationWarning,
+            "'__version__' is deprecated and slated for removal in Python 
3.20",
+        ) as cm:
+            getattr(ttk, "__version__")
+        self.assertEqual(cm.filename, __file__)
+
+
 def setUpModule():
     root = None
     try:
diff --git a/Lib/tkinter/font.py b/Lib/tkinter/font.py
index 3e24e28ef58cde..7aed523cce3784 100644
--- a/Lib/tkinter/font.py
+++ b/Lib/tkinter/font.py
@@ -6,7 +6,6 @@
 import itertools
 import tkinter
 
-__version__ = "0.9"
 __all__ = ["NORMAL", "ROMAN", "BOLD", "ITALIC",
            "nametofont", "Font", "families", "names"]
 
@@ -198,6 +197,15 @@ def names(root=None):
     return root.tk.splitlist(root.tk.call("font", "names"))
 
 
+def __getattr__(name):
+    if name == "__version__":
+        from warnings import _deprecated
+
+        _deprecated("__version__", remove=(3, 20))
+        return "0.9"  # Do not change
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+
+
 # --------------------------------------------------------------------
 # test stuff
 
diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py
index ef2b91dfbb6638..5c5ef11ae05ba6 100644
--- a/Lib/tkinter/ttk.py
+++ b/Lib/tkinter/ttk.py
@@ -12,8 +12,6 @@
 of the widgets appearance lies at Themes.
 """
 
-__version__ = "0.3.1"
-
 __author__ = "Guilherme Polo <[email protected]>"
 
 __all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label",
@@ -1648,3 +1646,12 @@ def destroy(self):
         except AttributeError:
             pass
         super().destroy()
+
+
+def __getattr__(name):
+    if name == "__version__":
+        from warnings import _deprecated
+
+        _deprecated("__version__", remove=(3, 20))
+        return "0.3.1"  # Do not change
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git 
a/Misc/NEWS.d/next/Library/2025-09-08-17-32-02.gh-issue-76007.peEgcr.rst 
b/Misc/NEWS.d/next/Library/2025-09-08-17-32-02.gh-issue-76007.peEgcr.rst
new file mode 100644
index 00000000000000..c6476f029cee47
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-09-08-17-32-02.gh-issue-76007.peEgcr.rst
@@ -0,0 +1,2 @@
+Deprecate ``__version__`` from a number of standard library modules. Patch
+by Hugo van Kemenade.
diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json
index 6ca09684a595a0..dc5497663a95bc 100644
--- a/Misc/sbom.spdx.json
+++ b/Misc/sbom.spdx.json
@@ -944,11 +944,11 @@
       "checksums": [
         {
           "algorithm": "SHA1",
-          "checksumValue": "0fbc026a9771d9675e7094790b5b945334d3cb53"
+          "checksumValue": "0d83ed429ede0a2c6617cd2f24490be16d8393f4"
         },
         {
           "algorithm": "SHA256",
-          "checksumValue": 
"1e77c01eec8f167ed10b754f153c0c743c8e5196ae9c81dffc08f129ab56dbfd"
+          "checksumValue": 
"876f4486d08d3eac3581a8681d7fb3cec2fe5b823d1bef27cca15e755510365c"
         }
       ],
       "fileName": "Lib/ctypes/macholib/__init__.py"

_______________________________________________
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