https://github.com/python/cpython/commit/8a00c9a4d2ce9d373b13f8f0a2265a65f4523293
commit: 8a00c9a4d2ce9d373b13f8f0a2265a65f4523293
branch: main
author: Thomas Grainger <tagr...@gmail.com>
committer: hugovk <1324225+hug...@users.noreply.github.com>
date: 2025-03-27T21:06:52+02:00
summary:

gh-128770: raise warnings as errors in test suite - except for test_socket 
which still logs warnings, and internal test warnings that are now logged 
(#128973)

Co-authored-by: Bénédikt Tran <10796600+picn...@users.noreply.github.com>

files:
A Lib/test/support/numbers.py
M Lib/test/libregrtest/main.py
M Lib/test/support/__init__.py
M Lib/test/support/os_helper.py
M Lib/test/test_complex.py
M Lib/test/test_decimal.py
M Lib/test/test_float.py
M Lib/test/test_grammar.py
M Lib/test/test_hashlib.py
M Lib/test/test_int.py
M Lib/test/test_interpreters/utils.py
M Lib/test/test_pty.py
M Lib/test/test_socket.py
M Lib/test/test_support.py
M Lib/test/test_tokenize.py

diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py
index a3638b46eaf8c4..19beb2919faafa 100644
--- a/Lib/test/libregrtest/main.py
+++ b/Lib/test/libregrtest/main.py
@@ -640,9 +640,9 @@ def _add_ci_python_opts(self, python_opts, keep_environ):
         if not sys.stdout.write_through:
             python_opts.append('-u')
 
-        # Add warnings filter 'default'
+        # Add warnings filter 'error'
         if 'default' not in sys.warnoptions:
-            python_opts.extend(('-W', 'default'))
+            python_opts.extend(('-W', 'error'))
 
         # Error on bytes/str comparison
         if sys.flags.bytes_warning < 2:
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 2b8920cc15b115..748b9b37117063 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -6,6 +6,7 @@
 import contextlib
 import functools
 import inspect
+import logging
 import _opcode
 import os
 import re
@@ -404,7 +405,7 @@ def skip_if_buildbot(reason=None):
     try:
         isbuildbot = getpass.getuser().lower() == 'buildbot'
     except (KeyError, OSError) as err:
-        warnings.warn(f'getpass.getuser() failed {err}.', RuntimeWarning)
+        logging.getLogger(__name__).warning('getpass.getuser() failed %s.', 
err, exc_info=err)
         isbuildbot = False
     return unittest.skipIf(isbuildbot, reason)
 
@@ -1089,8 +1090,7 @@ def start(self):
         try:
             f = open(self.procfile, 'r')
         except OSError as e:
-            warnings.warn('/proc not available for stats: {}'.format(e),
-                          RuntimeWarning)
+            logging.getLogger(__name__).warning('/proc not available for 
stats: %s', e, exc_info=e)
             sys.stderr.flush()
             return
 
diff --git a/Lib/test/support/numbers.py b/Lib/test/support/numbers.py
new file mode 100644
index 00000000000000..d5dbb41acebc38
--- /dev/null
+++ b/Lib/test/support/numbers.py
@@ -0,0 +1,80 @@
+# These are shared with test_tokenize and other test modules.
+#
+# Note: since several test cases filter out floats by looking for "e" and ".",
+# don't add hexadecimal literals that contain "e" or "E".
+VALID_UNDERSCORE_LITERALS = [
+    '0_0_0',
+    '4_2',
+    '1_0000_0000',
+    '0b1001_0100',
+    '0xffff_ffff',
+    '0o5_7_7',
+    '1_00_00.5',
+    '1_00_00.5e5',
+    '1_00_00e5_1',
+    '1e1_0',
+    '.1_4',
+    '.1_4e1',
+    '0b_0',
+    '0x_f',
+    '0o_5',
+    '1_00_00j',
+    '1_00_00.5j',
+    '1_00_00e5_1j',
+    '.1_4j',
+    '(1_2.5+3_3j)',
+    '(.5_6j)',
+]
+INVALID_UNDERSCORE_LITERALS = [
+    # Trailing underscores:
+    '0_',
+    '42_',
+    '1.4j_',
+    '0x_',
+    '0b1_',
+    '0xf_',
+    '0o5_',
+    '0 if 1_Else 1',
+    # Underscores in the base selector:
+    '0_b0',
+    '0_xf',
+    '0_o5',
+    # Old-style octal, still disallowed:
+    '0_7',
+    '09_99',
+    # Multiple consecutive underscores:
+    '4_______2',
+    '0.1__4',
+    '0.1__4j',
+    '0b1001__0100',
+    '0xffff__ffff',
+    '0x___',
+    '0o5__77',
+    '1e1__0',
+    '1e1__0j',
+    # Underscore right before a dot:
+    '1_.4',
+    '1_.4j',
+    # Underscore right after a dot:
+    '1._4',
+    '1._4j',
+    '._5',
+    '._5j',
+    # Underscore right after a sign:
+    '1.0e+_1',
+    '1.0e+_1j',
+    # Underscore right before j:
+    '1.4_j',
+    '1.4e5_j',
+    # Underscore right before e:
+    '1_e1',
+    '1.4_e1',
+    '1.4_e1j',
+    # Underscore right after e:
+    '1e_1',
+    '1.4e_1',
+    '1.4e_1j',
+    # Complex cases with parens:
+    '(1+1.5_j_)',
+    '(1+1.5_j)',
+]
diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py
index 4e39b5a835aac8..d82093e375c8b1 100644
--- a/Lib/test/support/os_helper.py
+++ b/Lib/test/support/os_helper.py
@@ -1,6 +1,7 @@
 import collections.abc
 import contextlib
 import errno
+import logging
 import os
 import re
 import stat
@@ -405,8 +406,12 @@ def _waitfor(func, pathname, waitall=False):
             # Increase the timeout and try again
             time.sleep(timeout)
             timeout *= 2
-        warnings.warn('tests may fail, delete still pending for ' + pathname,
-                      RuntimeWarning, stacklevel=4)
+        logging.getLogger(__name__).warning(
+            'tests may fail, delete still pending for %s',
+            pathname,
+            stack_info=True,
+            stacklevel=4,
+        )
 
     def _unlink(filename):
         _waitfor(os.unlink, filename)
@@ -521,9 +526,14 @@ def temp_dir(path=None, quiet=False):
         except OSError as exc:
             if not quiet:
                 raise
-            warnings.warn(f'tests may fail, unable to create '
-                          f'temporary directory {path!r}: {exc}',
-                          RuntimeWarning, stacklevel=3)
+            logging.getLogger(__name__).warning(
+                "tests may fail, unable to create temporary directory %r: %s",
+                path,
+                exc,
+                exc_info=exc,
+                stack_info=True,
+                stacklevel=3,
+            )
     if dir_created:
         pid = os.getpid()
     try:
@@ -554,9 +564,15 @@ def change_cwd(path, quiet=False):
     except OSError as exc:
         if not quiet:
             raise
-        warnings.warn(f'tests may fail, unable to change the current working '
-                      f'directory to {path!r}: {exc}',
-                      RuntimeWarning, stacklevel=3)
+        logging.getLogger(__name__).warning(
+            'tests may fail, unable to change the current working directory '
+            'to %r: %s',
+            path,
+            exc,
+            exc_info=exc,
+            stack_info=True,
+            stacklevel=3,
+        )
     try:
         yield os.getcwd()
     finally:
diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py
index fd002fb00ac338..0c7e7341f13d4e 100644
--- a/Lib/test/test_complex.py
+++ b/Lib/test/test_complex.py
@@ -2,8 +2,10 @@
 import sys
 from test import support
 from test.support.testcase import ComplexesAreIdenticalMixin
-from test.test_grammar import (VALID_UNDERSCORE_LITERALS,
-                               INVALID_UNDERSCORE_LITERALS)
+from test.support.numbers import (
+    VALID_UNDERSCORE_LITERALS,
+    INVALID_UNDERSCORE_LITERALS,
+)
 
 from random import random
 from math import isnan, copysign
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
index 0e15eb3693888d..d2327d247fa498 100644
--- a/Lib/test/test_decimal.py
+++ b/Lib/test/test_decimal.py
@@ -24,6 +24,7 @@
 with the corresponding argument.
 """
 
+import logging
 import math
 import os, sys
 import operator
@@ -5955,8 +5956,9 @@ def tearDownModule():
     if C: C.setcontext(ORIGINAL_CONTEXT[C].copy())
     P.setcontext(ORIGINAL_CONTEXT[P].copy())
     if not C:
-        warnings.warn('C tests skipped: no module named _decimal.',
-                      UserWarning)
+        logging.getLogger(__name__).warning(
+            'C tests skipped: no module named _decimal.'
+        )
     if not orig_sys_decimal is sys.modules['decimal']:
         raise TestFailed("Internal error: unbalanced number of changes to "
                          "sys.modules['decimal'].")
diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py
index ee8e28bf75b5a8..237d7b5d35edd7 100644
--- a/Lib/test/test_float.py
+++ b/Lib/test/test_float.py
@@ -9,8 +9,10 @@
 
 from test import support
 from test.support.testcase import FloatsAreIdenticalMixin
-from test.test_grammar import (VALID_UNDERSCORE_LITERALS,
-                               INVALID_UNDERSCORE_LITERALS)
+from test.support.numbers import (
+    VALID_UNDERSCORE_LITERALS,
+    INVALID_UNDERSCORE_LITERALS,
+)
 from math import isinf, isnan, copysign, ldexp
 import math
 
diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py
index bee2c06e274a96..3f1e1fa51fd386 100644
--- a/Lib/test/test_grammar.py
+++ b/Lib/test/test_grammar.py
@@ -18,88 +18,10 @@
 import typing
 from test.typinganndata import ann_module2
 import test
-
-# These are shared with test_tokenize and other test modules.
-#
-# Note: since several test cases filter out floats by looking for "e" and ".",
-# don't add hexadecimal literals that contain "e" or "E".
-VALID_UNDERSCORE_LITERALS = [
-    '0_0_0',
-    '4_2',
-    '1_0000_0000',
-    '0b1001_0100',
-    '0xffff_ffff',
-    '0o5_7_7',
-    '1_00_00.5',
-    '1_00_00.5e5',
-    '1_00_00e5_1',
-    '1e1_0',
-    '.1_4',
-    '.1_4e1',
-    '0b_0',
-    '0x_f',
-    '0o_5',
-    '1_00_00j',
-    '1_00_00.5j',
-    '1_00_00e5_1j',
-    '.1_4j',
-    '(1_2.5+3_3j)',
-    '(.5_6j)',
-]
-INVALID_UNDERSCORE_LITERALS = [
-    # Trailing underscores:
-    '0_',
-    '42_',
-    '1.4j_',
-    '0x_',
-    '0b1_',
-    '0xf_',
-    '0o5_',
-    '0 if 1_Else 1',
-    # Underscores in the base selector:
-    '0_b0',
-    '0_xf',
-    '0_o5',
-    # Old-style octal, still disallowed:
-    '0_7',
-    '09_99',
-    # Multiple consecutive underscores:
-    '4_______2',
-    '0.1__4',
-    '0.1__4j',
-    '0b1001__0100',
-    '0xffff__ffff',
-    '0x___',
-    '0o5__77',
-    '1e1__0',
-    '1e1__0j',
-    # Underscore right before a dot:
-    '1_.4',
-    '1_.4j',
-    # Underscore right after a dot:
-    '1._4',
-    '1._4j',
-    '._5',
-    '._5j',
-    # Underscore right after a sign:
-    '1.0e+_1',
-    '1.0e+_1j',
-    # Underscore right before j:
-    '1.4_j',
-    '1.4e5_j',
-    # Underscore right before e:
-    '1_e1',
-    '1.4_e1',
-    '1.4_e1j',
-    # Underscore right after e:
-    '1e_1',
-    '1.4e_1',
-    '1.4e_1j',
-    # Complex cases with parens:
-    '(1+1.5_j_)',
-    '(1+1.5_j)',
-]
-
+from test.support.numbers import (
+    VALID_UNDERSCORE_LITERALS,
+    INVALID_UNDERSCORE_LITERALS,
+)
 
 class TokenTests(unittest.TestCase):
 
diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py
index 0c1680cfbce36b..3840dd54dcf599 100644
--- a/Lib/test/test_hashlib.py
+++ b/Lib/test/test_hashlib.py
@@ -10,6 +10,7 @@
 import importlib
 import io
 import itertools
+import logging
 import os
 import sys
 import sysconfig
@@ -113,7 +114,11 @@ def _conditional_import_module(self, module_name):
             return importlib.import_module(module_name)
         except ModuleNotFoundError as error:
             if self._warn_on_extension_import and module_name in 
builtin_hashes:
-                warnings.warn(f'Did a C extension fail to compile? {error}')
+                logging.getLogger(__name__).warning(
+                    'Did a C extension fail to compile? %s',
+                    error,
+                    exc_info=error,
+                )
         return None
 
     def __init__(self, *args, **kwargs):
diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py
index 8870d7aa5d663d..d1bf847a7a3e9f 100644
--- a/Lib/test/test_int.py
+++ b/Lib/test/test_int.py
@@ -3,8 +3,10 @@
 import unittest
 from unittest import mock
 from test import support
-from test.test_grammar import (VALID_UNDERSCORE_LITERALS,
-                               INVALID_UNDERSCORE_LITERALS)
+from test.support.numbers import (
+    VALID_UNDERSCORE_LITERALS,
+    INVALID_UNDERSCORE_LITERALS,
+)
 
 try:
     import _pylong
diff --git a/Lib/test/test_interpreters/utils.py 
b/Lib/test/test_interpreters/utils.py
index 3cab76d0f279e0..fc4ad662e03b66 100644
--- a/Lib/test/test_interpreters/utils.py
+++ b/Lib/test/test_interpreters/utils.py
@@ -1,6 +1,7 @@
 from collections import namedtuple
 import contextlib
 import json
+import logging
 import os
 import os.path
 #import select
@@ -66,8 +67,8 @@ def pack_exception(exc=None):
 def unpack_exception(packed):
     try:
         data = json.loads(packed)
-    except json.decoder.JSONDecodeError:
-        warnings.warn('incomplete exception data', RuntimeWarning)
+    except json.decoder.JSONDecodeError as e:
+        logging.getLogger(__name__).warning('incomplete exception data', 
exc_info=e)
         print(packed if isinstance(packed, str) else packed.decode('utf-8'))
         return None
     exc = types.SimpleNamespace(**data)
diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py
index dee94533c74549..c1728f5019d042 100644
--- a/Lib/test/test_pty.py
+++ b/Lib/test/test_pty.py
@@ -135,8 +135,10 @@ def test_openpty(self):
                 new_dim = tty.tcgetwinsize(pty.STDIN_FILENO)
                 self.assertEqual(new_dim, target_dim,
                                  "pty.STDIN_FILENO window size unchanged")
-            except OSError:
-                warnings.warn("Failed to set pty.STDIN_FILENO window size.")
+            except OSError as e:
+                logging.getLogger(__name__).warning(
+                    "Failed to set pty.STDIN_FILENO window size.", exc_info=e,
+                )
                 pass
 
         try:
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
index 6572032c1d14bd..cd3497f0274cd6 100644
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -27,6 +27,7 @@
 import threading
 import time
 import traceback
+import warnings
 from weakref import proxy
 try:
     import multiprocessing
@@ -209,6 +210,24 @@ def socket_setdefaulttimeout(timeout):
         socket.setdefaulttimeout(old_timeout)
 
 
+@contextlib.contextmanager
+def downgrade_malformed_data_warning():
+    # This warning happens on macos and win, but does not always happen on 
linux.
+    if sys.platform not in {"win32", "darwin"}:
+        yield
+        return
+
+    with warnings.catch_warnings():
+        # TODO: gh-110012, we should investigate why this warning is happening
+        # and fix it properly.
+        warnings.filterwarnings(
+            action="always",
+            message="received malformed or improperly-truncated ancillary 
data",
+            category=RuntimeWarning,
+        )
+        yield
+
+
 HAVE_SOCKET_CAN = _have_socket_can()
 
 HAVE_SOCKET_CAN_ISOTP = _have_socket_can_isotp()
@@ -3991,8 +4010,9 @@ def checkTruncatedArray(self, ancbuf, maxdata, mindata=0):
         # mindata and maxdata bytes when received with buffer size
         # ancbuf, and that any complete file descriptor numbers are
         # valid.
-        msg, ancdata, flags, addr = self.doRecvmsg(self.serv_sock,
-                                                   len(MSG), ancbuf)
+        with downgrade_malformed_data_warning():  # TODO: gh-110012
+            msg, ancdata, flags, addr = self.doRecvmsg(self.serv_sock,
+                                                       len(MSG), ancbuf)
         self.assertEqual(msg, MSG)
         self.checkRecvmsgAddress(addr, self.cli_addr)
         self.checkFlags(flags, eor=True, checkset=socket.MSG_CTRUNC)
@@ -4343,8 +4363,9 @@ def testSingleCmsgTruncInData(self):
         self.serv_sock.setsockopt(socket.IPPROTO_IPV6,
                                   socket.IPV6_RECVHOPLIMIT, 1)
         self.misc_event.set()
-        msg, ancdata, flags, addr = self.doRecvmsg(
-            self.serv_sock, len(MSG), socket.CMSG_LEN(SIZEOF_INT) - 1)
+        with downgrade_malformed_data_warning():  # TODO: gh-110012
+            msg, ancdata, flags, addr = self.doRecvmsg(
+                self.serv_sock, len(MSG), socket.CMSG_LEN(SIZEOF_INT) - 1)
 
         self.assertEqual(msg, MSG)
         self.checkRecvmsgAddress(addr, self.cli_addr)
@@ -4447,9 +4468,10 @@ def testSecondCmsgTruncInData(self):
         self.serv_sock.setsockopt(socket.IPPROTO_IPV6,
                                   socket.IPV6_RECVTCLASS, 1)
         self.misc_event.set()
-        msg, ancdata, flags, addr = self.doRecvmsg(
-            self.serv_sock, len(MSG),
-            socket.CMSG_SPACE(SIZEOF_INT) + socket.CMSG_LEN(SIZEOF_INT) - 1)
+        with downgrade_malformed_data_warning():  # TODO: gh-110012
+            msg, ancdata, flags, addr = self.doRecvmsg(
+                self.serv_sock, len(MSG),
+                socket.CMSG_SPACE(SIZEOF_INT) + socket.CMSG_LEN(SIZEOF_INT) - 
1)
 
         self.assertEqual(msg, MSG)
         self.checkRecvmsgAddress(addr, self.cli_addr)
diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py
index 8d5b3440d3bd30..efe6b77a7faa18 100644
--- a/Lib/test/test_support.py
+++ b/Lib/test/test_support.py
@@ -1,6 +1,8 @@
+import contextlib
 import errno
 import importlib
 import io
+import logging
 import os
 import shutil
 import signal
@@ -24,6 +26,31 @@
 TESTFN = os_helper.TESTFN
 
 
+class LogCaptureHandler(logging.StreamHandler):
+    # Inspired by pytest's caplog
+    def __init__(self):
+        super().__init__(io.StringIO())
+        self.records = []
+
+    def emit(self, record) -> None:
+        self.records.append(record)
+        super().emit(record)
+
+    def handleError(self, record):
+        raise
+
+
+@contextlib.contextmanager
+def _caplog():
+    handler = LogCaptureHandler()
+    root_logger = logging.getLogger()
+    root_logger.addHandler(handler)
+    try:
+        yield handler
+    finally:
+        root_logger.removeHandler(handler)
+
+
 class TestSupport(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
@@ -187,7 +214,7 @@ def test_temp_dir__existing_dir__quiet_true(self):
         path = os.path.realpath(path)
 
         try:
-            with warnings_helper.check_warnings() as recorder:
+            with warnings_helper.check_warnings() as recorder, _caplog() as 
caplog:
                 with os_helper.temp_dir(path, quiet=True) as temp_path:
                     self.assertEqual(path, temp_path)
                 warnings = [str(w.message) for w in recorder.warnings]
@@ -196,11 +223,14 @@ def test_temp_dir__existing_dir__quiet_true(self):
         finally:
             shutil.rmtree(path)
 
-        self.assertEqual(len(warnings), 1, warnings)
-        warn = warnings[0]
-        self.assertTrue(warn.startswith(f'tests may fail, unable to create '
-                                        f'temporary directory {path!r}: '),
-                        warn)
+        self.assertListEqual(warnings, [])
+        self.assertEqual(len(caplog.records), 1)
+        record = caplog.records[0]
+        self.assertStartsWith(
+            record.getMessage(),
+            f'tests may fail, unable to create '
+            f'temporary directory {path!r}: '
+        )
 
     @support.requires_fork()
     def test_temp_dir__forked_child(self):
@@ -260,35 +290,41 @@ def test_change_cwd__non_existent_dir__quiet_true(self):
 
         with os_helper.temp_dir() as parent_dir:
             bad_dir = os.path.join(parent_dir, 'does_not_exist')
-            with warnings_helper.check_warnings() as recorder:
+            with warnings_helper.check_warnings() as recorder, _caplog() as 
caplog:
                 with os_helper.change_cwd(bad_dir, quiet=True) as new_cwd:
                     self.assertEqual(new_cwd, original_cwd)
                     self.assertEqual(os.getcwd(), new_cwd)
                 warnings = [str(w.message) for w in recorder.warnings]
 
-        self.assertEqual(len(warnings), 1, warnings)
-        warn = warnings[0]
-        self.assertTrue(warn.startswith(f'tests may fail, unable to change '
-                                        f'the current working directory '
-                                        f'to {bad_dir!r}: '),
-                        warn)
+        self.assertListEqual(warnings, [])
+        self.assertEqual(len(caplog.records), 1)
+        record = caplog.records[0]
+        self.assertStartsWith(
+            record.getMessage(),
+            f'tests may fail, unable to change '
+            f'the current working directory '
+            f'to {bad_dir!r}: '
+        )
 
     # Tests for change_cwd()
 
     def test_change_cwd__chdir_warning(self):
         """Check the warning message when os.chdir() fails."""
         path = TESTFN + '_does_not_exist'
-        with warnings_helper.check_warnings() as recorder:
+        with warnings_helper.check_warnings() as recorder, _caplog() as caplog:
             with os_helper.change_cwd(path=path, quiet=True):
                 pass
             messages = [str(w.message) for w in recorder.warnings]
 
-        self.assertEqual(len(messages), 1, messages)
-        msg = messages[0]
-        self.assertTrue(msg.startswith(f'tests may fail, unable to change '
-                                       f'the current working directory '
-                                       f'to {path!r}: '),
-                        msg)
+        self.assertListEqual(messages, [])
+        self.assertEqual(len(caplog.records), 1)
+        record = caplog.records[0]
+        self.assertStartsWith(
+            record.getMessage(),
+            f'tests may fail, unable to change '
+            f'the current working directory '
+            f'to {path!r}: ',
+        )
 
     # Tests for temp_cwd()
 
diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py
index df2617c680b5e5..4e05a38394cdc1 100644
--- a/Lib/test/test_tokenize.py
+++ b/Lib/test/test_tokenize.py
@@ -9,10 +9,13 @@
 from textwrap import dedent
 from unittest import TestCase, mock
 from test import support
-from test.test_grammar import (VALID_UNDERSCORE_LITERALS,
-                               INVALID_UNDERSCORE_LITERALS)
 from test.support import os_helper
 from test.support.script_helper import run_test_script, make_script, 
run_python_until_end
+from test.support.numbers import (
+    VALID_UNDERSCORE_LITERALS,
+    INVALID_UNDERSCORE_LITERALS,
+)
+
 
 # Converts a source string into a list of textual representation
 # of the tokens such as:

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to