From 8de6eba6d7bcbe9bce0264edacae00675430a535 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 19 Feb 2022 14:20:14 +0200 Subject: [PATCH 01/20] bpo-46797: Emit deprecation warnings for deprecated ast features --- Lib/ast.py | 68 ++- Lib/test/test_ast.py | 467 +++++++++++++----- .../2022-02-19-14-19-34.bpo-46797.6BXZX4.rst | 2 + 3 files changed, 402 insertions(+), 135 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst diff --git a/Lib/ast.py b/Lib/ast.py index 625738ad681af4..070552576e44ad 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -284,9 +284,7 @@ def get_docstring(node, clean=True): if not(node.body and isinstance(node.body[0], Expr)): return None node = node.body[0].value - if isinstance(node, Str): - text = node.s - elif isinstance(node, Constant) and isinstance(node.value, str): + if isinstance(node, Constant) and isinstance(node.value, str): text = node.value else: return None @@ -505,15 +503,34 @@ def generic_visit(self, node): # The following code is for backward compatibility. # It will be removed in future. - def _getter(self): + def _n_getter(self): """Deprecated. Use value instead.""" + import warnings + warnings.warn("Attribute n is deprecated; use value instead", + DeprecationWarning, stacklevel=2) return self.value - def _setter(self, value): + def _n_setter(self, value): + import warnings + warnings.warn("Attribute n is deprecated; use value instead", + DeprecationWarning, stacklevel=2) self.value = value - Constant.n = property(_getter, _setter) - Constant.s = property(_getter, _setter) + def _s_getter(self): + """Deprecated. Use value instead.""" + import warnings + warnings.warn("Attribute s is deprecated; use value instead", + DeprecationWarning, stacklevel=2) + return self.value + + def _s_setter(self, value): + import warnings + warnings.warn("Attribute s is deprecated; use value instead", + DeprecationWarning, stacklevel=2) + self.value = value + + Constant.n = property(_n_getter, _n_setter) + Constant.s = property(_s_getter, _s_setter) class _ABC(type): @@ -544,6 +561,9 @@ def _new(cls, *args, **kwargs): if pos < len(args): raise TypeError(f"{cls.__name__} got multiple values for argument {key!r}") if cls in _const_types: + import warnings + warnings.warn(f"ast.{cls.__qualname__} is deprecated; use ast.Constant instead", + DeprecationWarning, stacklevel=2) return Constant(*args, **kwargs) return Constant.__new__(cls, *args, **kwargs) @@ -566,10 +586,15 @@ class Ellipsis(Constant, metaclass=_ABC): _fields = () def __new__(cls, *args, **kwargs): - if cls is Ellipsis: + if cls is _ast_Ellipsis: + import warnings + warnings.warn("ast.Ellipsis is deprecated; use ast.Constant instead", + DeprecationWarning, stacklevel=2) return Constant(..., *args, **kwargs) return Constant.__new__(cls, *args, **kwargs) +_ast_Ellipsis = Ellipsis + _const_types = { Num: (int, float, complex), Str: (str,), @@ -603,6 +628,9 @@ def __new__(cls, value, **kwargs): class ExtSlice(slice): """Deprecated AST node class. Use ast.Tuple instead.""" def __new__(cls, dims=(), **kwargs): + import warnings + warnings.warn("ast.ExtSlice is deprecated; use ast.Tuple instead", + DeprecationWarning, stacklevel=2) return Tuple(list(dims), Load(), **kwargs) # If the ast module is loaded more than once, only add deprecated methods once @@ -612,9 +640,14 @@ def __new__(cls, dims=(), **kwargs): def _dims_getter(self): """Deprecated. Use elts instead.""" + import warnings + warnings.warn("Attribute dims is deprecated; use elts instead", + DeprecationWarning, stacklevel=2) return self.elts def _dims_setter(self, value): + warnings.warn("Attribute dims is deprecated, use elts instead", + DeprecationWarning, stacklevel=2) self.elts = value Tuple.dims = property(_dims_getter, _dims_setter) @@ -1699,6 +1732,25 @@ def unparse(ast_obj): return unparser.visit(ast_obj) +_deprecated_globals = { + name: (globals().pop(name), '; use ast.Constant instead') + for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis') +} +_deprecated_globals['slice'] = globals().pop('slice'), '' +_deprecated_globals['Index'] = globals().pop('Index'), '' +_deprecated_globals['ExtSlice'] = globals().pop('ExtSlice'), '; use ast.Tuple instead' + +def __getattr__(name): + if name in _deprecated_globals: + value, details = _deprecated_globals[name] + globals()[name] = value + import warnings + warnings.warn(f"ast.{name} is deprecated{details}", + DeprecationWarning, stacklevel=2) + return value + raise AttributeError(f"module 'ast' has no attribute '{name}'") + + def main(): import argparse diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 039d1c1010b6d1..f50e55afc30f82 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -11,6 +11,7 @@ from textwrap import dedent from test import support +from test.support.import_helper import import_fresh_module def to_tuple(t): if t is None or isinstance(t, (str, int, complex)): @@ -251,6 +252,7 @@ def to_tuple(t): # excepthandler, arguments, keywords, alias class AST_Tests(unittest.TestCase): + maxDiff = None def _is_ast_node(self, name, node): if not isinstance(node, type): @@ -366,13 +368,48 @@ def test_base_classes(self): self.assertTrue(issubclass(ast.comprehension, ast.AST)) self.assertTrue(issubclass(ast.Gt, ast.AST)) - def test_field_attr_existence(self): - for name, item in ast.__dict__.items(): + def test_import_deprecated(self): + ast = import_fresh_module('ast') + for name in 'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis': + with self.assertWarnsRegex(DeprecationWarning, + fr'ast\.{name} is deprecated; use ast\.Constant instead'): + getattr(ast, name) + with self.assertWarnsRegex(DeprecationWarning, + r'ast\.slice is deprecated'): + ast.slice + with self.assertWarnsRegex(DeprecationWarning, + r'ast\.Index is deprecated'): + ast.Index + with self.assertWarnsRegex(DeprecationWarning, + r'ast\.ExtSlice is deprecated; use ast\.Tuple instead'): + ast.ExtSlice + + def test_field_attr_existence_deprecated(self): + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import Num, Str, Bytes, NameConstant, Ellipsis + from ast import slice, Index, ExtSlice + + for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis', + 'slice', 'Index', 'ExtSlice'): + item = getattr(ast, name) if self._is_ast_node(name, item): if name == 'Index': # Index(value) just returns value now. # The argument is required. continue + with self.subTest(item): + with self.assertWarns(DeprecationWarning): + x = item() + if isinstance(x, ast.AST): + self.assertEqual(type(x._fields), tuple) + + def test_field_attr_existence(self): + for name, item in ast.__dict__.items(): + if name in {'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis', + 'slice', 'Index', 'ExtSlice'}: + continue + if self._is_ast_node(name, item): x = item() if isinstance(x, ast.AST): self.assertEqual(type(x._fields), tuple) @@ -390,25 +427,108 @@ def test_arguments(self): self.assertEqual(x.args, 2) self.assertEqual(x.vararg, 3) + def test_field_attr_writable_deprecated(self): + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', '', DeprecationWarning) + x = ast.Num() + # We can assign to _fields + x._fields = 666 + self.assertEqual(x._fields, 666) + def test_field_attr_writable(self): - x = ast.Num() + x = ast.Constant() # We can assign to _fields x._fields = 666 self.assertEqual(x._fields, 666) + def test_classattrs_deprecated(self): + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import Num, Str, Bytes, NameConstant, Ellipsis + + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('always', '', DeprecationWarning) + x = ast.Num() + self.assertEqual(x._fields, ('value', 'kind')) + + with self.assertRaises(AttributeError): + x.value + + with self.assertRaises(AttributeError): + x.n + + x = ast.Num(42) + self.assertEqual(x.value, 42) + self.assertEqual(x.n, 42) + + with self.assertRaises(AttributeError): + x.lineno + + with self.assertRaises(AttributeError): + x.foobar + + x = ast.Num(lineno=2) + self.assertEqual(x.lineno, 2) + + x = ast.Num(42, lineno=0) + self.assertEqual(x.lineno, 0) + self.assertEqual(x._fields, ('value', 'kind')) + self.assertEqual(x.value, 42) + self.assertEqual(x.n, 42) + + self.assertRaises(TypeError, ast.Num, 1, None, 2) + self.assertRaises(TypeError, ast.Num, 1, None, 2, lineno=0) + + # Arbitrary keyword arguments are supported + self.assertEqual(ast.Num(1, foo='bar').foo, 'bar') + + with self.assertRaisesRegex(TypeError, "Num got multiple values for argument 'n'"): + ast.Num(1, n=2) + + self.assertEqual(ast.Num(42).n, 42) + self.assertEqual(ast.Num(4.25).n, 4.25) + self.assertEqual(ast.Num(4.25j).n, 4.25j) + self.assertEqual(ast.Str('42').s, '42') + self.assertEqual(ast.Bytes(b'42').s, b'42') + self.assertIs(ast.NameConstant(True).value, True) + self.assertIs(ast.NameConstant(False).value, False) + self.assertIs(ast.NameConstant(None).value, None) + + self.assertEqual([str(w.message) for w in wlog], [ + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'ast.Str is deprecated; use ast.Constant instead', + 'Attribute s is deprecated; use value instead', + 'ast.Bytes is deprecated; use ast.Constant instead', + 'Attribute s is deprecated; use value instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + ]) + def test_classattrs(self): - x = ast.Num() + x = ast.Constant() self.assertEqual(x._fields, ('value', 'kind')) with self.assertRaises(AttributeError): x.value - with self.assertRaises(AttributeError): - x.n - - x = ast.Num(42) + x = ast.Constant(42) self.assertEqual(x.value, 42) - self.assertEqual(x.n, 42) with self.assertRaises(AttributeError): x.lineno @@ -416,36 +536,23 @@ def test_classattrs(self): with self.assertRaises(AttributeError): x.foobar - x = ast.Num(lineno=2) + x = ast.Constant(lineno=2) self.assertEqual(x.lineno, 2) - x = ast.Num(42, lineno=0) + x = ast.Constant(42, lineno=0) self.assertEqual(x.lineno, 0) self.assertEqual(x._fields, ('value', 'kind')) self.assertEqual(x.value, 42) - self.assertEqual(x.n, 42) - self.assertRaises(TypeError, ast.Num, 1, None, 2) - self.assertRaises(TypeError, ast.Num, 1, None, 2, lineno=0) + self.assertRaises(TypeError, ast.Constant, 1, None, 2) + self.assertRaises(TypeError, ast.Constant, 1, None, 2, lineno=0) # Arbitrary keyword arguments are supported self.assertEqual(ast.Constant(1, foo='bar').foo, 'bar') - self.assertEqual(ast.Num(1, foo='bar').foo, 'bar') - with self.assertRaisesRegex(TypeError, "Num got multiple values for argument 'n'"): - ast.Num(1, n=2) with self.assertRaisesRegex(TypeError, "Constant got multiple values for argument 'value'"): ast.Constant(1, value=2) - self.assertEqual(ast.Num(42).n, 42) - self.assertEqual(ast.Num(4.25).n, 4.25) - self.assertEqual(ast.Num(4.25j).n, 4.25j) - self.assertEqual(ast.Str('42').s, '42') - self.assertEqual(ast.Bytes(b'42').s, b'42') - self.assertIs(ast.NameConstant(True).value, True) - self.assertIs(ast.NameConstant(False).value, False) - self.assertIs(ast.NameConstant(None).value, None) - self.assertEqual(ast.Constant(42).value, 42) self.assertEqual(ast.Constant(4.25).value, 4.25) self.assertEqual(ast.Constant(4.25j).value, 4.25j) @@ -457,85 +564,162 @@ def test_classattrs(self): self.assertIs(ast.Constant(...).value, ...) def test_realtype(self): - self.assertEqual(type(ast.Num(42)), ast.Constant) - self.assertEqual(type(ast.Num(4.25)), ast.Constant) - self.assertEqual(type(ast.Num(4.25j)), ast.Constant) - self.assertEqual(type(ast.Str('42')), ast.Constant) - self.assertEqual(type(ast.Bytes(b'42')), ast.Constant) - self.assertEqual(type(ast.NameConstant(True)), ast.Constant) - self.assertEqual(type(ast.NameConstant(False)), ast.Constant) - self.assertEqual(type(ast.NameConstant(None)), ast.Constant) - self.assertEqual(type(ast.Ellipsis()), ast.Constant) + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import Num, Str, Bytes, NameConstant, Ellipsis + + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('always', '', DeprecationWarning) + self.assertEqual(type(ast.Num(42)), ast.Constant) + self.assertEqual(type(ast.Num(4.25)), ast.Constant) + self.assertEqual(type(ast.Num(4.25j)), ast.Constant) + self.assertEqual(type(ast.Str('42')), ast.Constant) + self.assertEqual(type(ast.Bytes(b'42')), ast.Constant) + self.assertEqual(type(ast.NameConstant(True)), ast.Constant) + self.assertEqual(type(ast.NameConstant(False)), ast.Constant) + self.assertEqual(type(ast.NameConstant(None)), ast.Constant) + self.assertEqual(type(ast.Ellipsis()), ast.Constant) + + self.assertEqual([str(w.message) for w in wlog], [ + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Str is deprecated; use ast.Constant instead', + 'ast.Bytes is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.Ellipsis is deprecated; use ast.Constant instead', + ]) def test_isinstance(self): - self.assertTrue(isinstance(ast.Num(42), ast.Num)) - self.assertTrue(isinstance(ast.Num(4.2), ast.Num)) - self.assertTrue(isinstance(ast.Num(4.2j), ast.Num)) - self.assertTrue(isinstance(ast.Str('42'), ast.Str)) - self.assertTrue(isinstance(ast.Bytes(b'42'), ast.Bytes)) - self.assertTrue(isinstance(ast.NameConstant(True), ast.NameConstant)) - self.assertTrue(isinstance(ast.NameConstant(False), ast.NameConstant)) - self.assertTrue(isinstance(ast.NameConstant(None), ast.NameConstant)) - self.assertTrue(isinstance(ast.Ellipsis(), ast.Ellipsis)) - - self.assertTrue(isinstance(ast.Constant(42), ast.Num)) - self.assertTrue(isinstance(ast.Constant(4.2), ast.Num)) - self.assertTrue(isinstance(ast.Constant(4.2j), ast.Num)) - self.assertTrue(isinstance(ast.Constant('42'), ast.Str)) - self.assertTrue(isinstance(ast.Constant(b'42'), ast.Bytes)) - self.assertTrue(isinstance(ast.Constant(True), ast.NameConstant)) - self.assertTrue(isinstance(ast.Constant(False), ast.NameConstant)) - self.assertTrue(isinstance(ast.Constant(None), ast.NameConstant)) - self.assertTrue(isinstance(ast.Constant(...), ast.Ellipsis)) - - self.assertFalse(isinstance(ast.Str('42'), ast.Num)) - self.assertFalse(isinstance(ast.Num(42), ast.Str)) - self.assertFalse(isinstance(ast.Str('42'), ast.Bytes)) - self.assertFalse(isinstance(ast.Num(42), ast.NameConstant)) - self.assertFalse(isinstance(ast.Num(42), ast.Ellipsis)) - self.assertFalse(isinstance(ast.NameConstant(True), ast.Num)) - self.assertFalse(isinstance(ast.NameConstant(False), ast.Num)) - - self.assertFalse(isinstance(ast.Constant('42'), ast.Num)) - self.assertFalse(isinstance(ast.Constant(42), ast.Str)) - self.assertFalse(isinstance(ast.Constant('42'), ast.Bytes)) - self.assertFalse(isinstance(ast.Constant(42), ast.NameConstant)) - self.assertFalse(isinstance(ast.Constant(42), ast.Ellipsis)) - self.assertFalse(isinstance(ast.Constant(True), ast.Num)) - self.assertFalse(isinstance(ast.Constant(False), ast.Num)) - - self.assertFalse(isinstance(ast.Constant(), ast.Num)) - self.assertFalse(isinstance(ast.Constant(), ast.Str)) - self.assertFalse(isinstance(ast.Constant(), ast.Bytes)) - self.assertFalse(isinstance(ast.Constant(), ast.NameConstant)) - self.assertFalse(isinstance(ast.Constant(), ast.Ellipsis)) - - class S(str): pass - self.assertTrue(isinstance(ast.Constant(S('42')), ast.Str)) - self.assertFalse(isinstance(ast.Constant(S('42')), ast.Num)) - - def test_subclasses(self): - class N(ast.Num): + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import Num, Str, Bytes, NameConstant, Ellipsis + + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('always', '', DeprecationWarning) + self.assertTrue(isinstance(ast.Num(42), ast.Num)) + self.assertTrue(isinstance(ast.Num(4.2), ast.Num)) + self.assertTrue(isinstance(ast.Num(4.2j), ast.Num)) + self.assertTrue(isinstance(ast.Str('42'), ast.Str)) + self.assertTrue(isinstance(ast.Bytes(b'42'), ast.Bytes)) + self.assertTrue(isinstance(ast.NameConstant(True), ast.NameConstant)) + self.assertTrue(isinstance(ast.NameConstant(False), ast.NameConstant)) + self.assertTrue(isinstance(ast.NameConstant(None), ast.NameConstant)) + self.assertTrue(isinstance(ast.Ellipsis(), ast.Ellipsis)) + + self.assertTrue(isinstance(ast.Constant(42), ast.Num)) + self.assertTrue(isinstance(ast.Constant(4.2), ast.Num)) + self.assertTrue(isinstance(ast.Constant(4.2j), ast.Num)) + self.assertTrue(isinstance(ast.Constant('42'), ast.Str)) + self.assertTrue(isinstance(ast.Constant(b'42'), ast.Bytes)) + self.assertTrue(isinstance(ast.Constant(True), ast.NameConstant)) + self.assertTrue(isinstance(ast.Constant(False), ast.NameConstant)) + self.assertTrue(isinstance(ast.Constant(None), ast.NameConstant)) + self.assertTrue(isinstance(ast.Constant(...), ast.Ellipsis)) + + self.assertFalse(isinstance(ast.Str('42'), ast.Num)) + self.assertFalse(isinstance(ast.Num(42), ast.Str)) + self.assertFalse(isinstance(ast.Str('42'), ast.Bytes)) + self.assertFalse(isinstance(ast.Num(42), ast.NameConstant)) + self.assertFalse(isinstance(ast.Num(42), ast.Ellipsis)) + self.assertFalse(isinstance(ast.NameConstant(True), ast.Num)) + self.assertFalse(isinstance(ast.NameConstant(False), ast.Num)) + + self.assertFalse(isinstance(ast.Constant('42'), ast.Num)) + self.assertFalse(isinstance(ast.Constant(42), ast.Str)) + self.assertFalse(isinstance(ast.Constant('42'), ast.Bytes)) + self.assertFalse(isinstance(ast.Constant(42), ast.NameConstant)) + self.assertFalse(isinstance(ast.Constant(42), ast.Ellipsis)) + self.assertFalse(isinstance(ast.Constant(True), ast.Num)) + self.assertFalse(isinstance(ast.Constant(False), ast.Num)) + + self.assertFalse(isinstance(ast.Constant(), ast.Num)) + self.assertFalse(isinstance(ast.Constant(), ast.Str)) + self.assertFalse(isinstance(ast.Constant(), ast.Bytes)) + self.assertFalse(isinstance(ast.Constant(), ast.NameConstant)) + self.assertFalse(isinstance(ast.Constant(), ast.Ellipsis)) + + class S(str): pass + self.assertTrue(isinstance(ast.Constant(S('42')), ast.Str)) + self.assertFalse(isinstance(ast.Constant(S('42')), ast.Num)) + + self.assertEqual([str(w.message) for w in wlog], [ + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Str is deprecated; use ast.Constant instead', + 'ast.Bytes is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.Ellipsis is deprecated; use ast.Constant instead', + 'ast.Str is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Str is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + ]) + + def test_constant_subclasses_deprecated(self): + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import Num + + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('always', '', DeprecationWarning) + class N(ast.Num): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.z = 'spam' + class N2(ast.Num): + pass + + n = N(42) + self.assertEqual(n.n, 42) + self.assertEqual(n.z, 'spam') + self.assertEqual(type(n), N) + self.assertTrue(isinstance(n, N)) + self.assertTrue(isinstance(n, ast.Num)) + self.assertFalse(isinstance(n, N2)) + self.assertFalse(isinstance(ast.Num(42), N)) + n = N(n=42) + self.assertEqual(n.n, 42) + self.assertEqual(type(n), N) + + self.assertEqual([str(w.message) for w in wlog], [ + 'Attribute n is deprecated; use value instead', + 'Attribute n is deprecated; use value instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'Attribute n is deprecated; use value instead', + ]) + + def test_constant_subclasses(self): + class N(ast.Constant): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.z = 'spam' - class N2(ast.Num): + class N2(ast.Constant): pass n = N(42) - self.assertEqual(n.n, 42) + self.assertEqual(n.value, 42) self.assertEqual(n.z, 'spam') self.assertEqual(type(n), N) self.assertTrue(isinstance(n, N)) - self.assertTrue(isinstance(n, ast.Num)) + self.assertTrue(isinstance(n, ast.Constant)) self.assertFalse(isinstance(n, N2)) - self.assertFalse(isinstance(ast.Num(42), N)) - n = N(n=42) - self.assertEqual(n.n, 42) + self.assertFalse(isinstance(ast.Constant(42), N)) + n = N(value=42) + self.assertEqual(n.value, 42) self.assertEqual(type(n), N) def test_module(self): - body = [ast.Num(42)] + body = [ast.Constant(42)] x = ast.Module(body, []) self.assertEqual(x.body, body) @@ -548,8 +732,8 @@ def test_nodeclasses(self): x.foobarbaz = 5 self.assertEqual(x.foobarbaz, 5) - n1 = ast.Num(1) - n3 = ast.Num(3) + n1 = ast.Constant(1) + n3 = ast.Constant(3) addop = ast.Add() x = ast.BinOp(n1, addop, n3) self.assertEqual(x.left, n1) @@ -861,7 +1045,7 @@ def test_dump_incomplete(self): def test_copy_location(self): src = ast.parse('1 + 1', mode='eval') - src.body.right = ast.copy_location(ast.Num(2), src.body.right) + src.body.right = ast.copy_location(ast.Constant(2), src.body.right) self.assertEqual(ast.dump(src, include_attributes=True), 'Expression(body=BinOp(left=Constant(value=1, lineno=1, col_offset=0, ' 'end_lineno=1, end_col_offset=1), op=Add(), right=Constant(value=2, ' @@ -878,7 +1062,7 @@ def test_copy_location(self): def test_fix_missing_locations(self): src = ast.parse('write("spam")') src.body.append(ast.Expr(ast.Call(ast.Name('spam', ast.Load()), - [ast.Str('eggs')], []))) + [ast.Constant('eggs')], []))) self.assertEqual(src, ast.fix_missing_locations(src)) self.maxDiff = None self.assertEqual(ast.dump(src, include_attributes=True), @@ -1171,9 +1355,9 @@ def arguments(args=None, posonlyargs=None, vararg=None, check(arguments(args=args), "must have Load context") check(arguments(posonlyargs=args), "must have Load context") check(arguments(kwonlyargs=args), "must have Load context") - check(arguments(defaults=[ast.Num(3)]), + check(arguments(defaults=[ast.Constant(3)]), "more positional defaults than args") - check(arguments(kw_defaults=[ast.Num(4)]), + check(arguments(kw_defaults=[ast.Constant(4)]), "length of kwonlyargs is not the same as kw_defaults") args = [ast.arg("x", ast.Name("x", ast.Load()))] check(arguments(args=args, defaults=[ast.Name("x", ast.Store())]), @@ -1226,9 +1410,9 @@ def test_delete(self): "must have Del context") def test_assign(self): - self.stmt(ast.Assign([], ast.Num(3)), "empty targets on Assign") - self.stmt(ast.Assign([None], ast.Num(3)), "None disallowed") - self.stmt(ast.Assign([ast.Name("x", ast.Load())], ast.Num(3)), + self.stmt(ast.Assign([], ast.Constant(3)), "empty targets on Assign") + self.stmt(ast.Assign([None], ast.Constant(3)), "None disallowed") + self.stmt(ast.Assign([ast.Name("x", ast.Load())], ast.Constant(3)), "must have Store context") self.stmt(ast.Assign([ast.Name("x", ast.Store())], ast.Name("y", ast.Store())), @@ -1256,39 +1440,39 @@ def test_for(self): self.stmt(ast.For(x, y, [p], [e]), "must have Load context") def test_while(self): - self.stmt(ast.While(ast.Num(3), [], []), "empty body on While") + self.stmt(ast.While(ast.Constant(3), [], []), "empty body on While") self.stmt(ast.While(ast.Name("x", ast.Store()), [ast.Pass()], []), "must have Load context") - self.stmt(ast.While(ast.Num(3), [ast.Pass()], + self.stmt(ast.While(ast.Constant(3), [ast.Pass()], [ast.Expr(ast.Name("x", ast.Store()))]), "must have Load context") def test_if(self): - self.stmt(ast.If(ast.Num(3), [], []), "empty body on If") + self.stmt(ast.If(ast.Constant(3), [], []), "empty body on If") i = ast.If(ast.Name("x", ast.Store()), [ast.Pass()], []) self.stmt(i, "must have Load context") - i = ast.If(ast.Num(3), [ast.Expr(ast.Name("x", ast.Store()))], []) + i = ast.If(ast.Constant(3), [ast.Expr(ast.Name("x", ast.Store()))], []) self.stmt(i, "must have Load context") - i = ast.If(ast.Num(3), [ast.Pass()], + i = ast.If(ast.Constant(3), [ast.Pass()], [ast.Expr(ast.Name("x", ast.Store()))]) self.stmt(i, "must have Load context") def test_with(self): p = ast.Pass() self.stmt(ast.With([], [p]), "empty items on With") - i = ast.withitem(ast.Num(3), None) + i = ast.withitem(ast.Constant(3), None) self.stmt(ast.With([i], []), "empty body on With") i = ast.withitem(ast.Name("x", ast.Store()), None) self.stmt(ast.With([i], [p]), "must have Load context") - i = ast.withitem(ast.Num(3), ast.Name("x", ast.Load())) + i = ast.withitem(ast.Constant(3), ast.Name("x", ast.Load())) self.stmt(ast.With([i], [p]), "must have Store context") def test_raise(self): - r = ast.Raise(None, ast.Num(3)) + r = ast.Raise(None, ast.Constant(3)) self.stmt(r, "Raise with cause but no exception") r = ast.Raise(ast.Name("x", ast.Store()), None) self.stmt(r, "must have Load context") - r = ast.Raise(ast.Num(4), ast.Name("x", ast.Store())) + r = ast.Raise(ast.Constant(4), ast.Name("x", ast.Store())) self.stmt(r, "must have Load context") def test_try(self): @@ -1359,11 +1543,11 @@ def test_expr(self): def test_boolop(self): b = ast.BoolOp(ast.And(), []) self.expr(b, "less than 2 values") - b = ast.BoolOp(ast.And(), [ast.Num(3)]) + b = ast.BoolOp(ast.And(), [ast.Constant(3)]) self.expr(b, "less than 2 values") - b = ast.BoolOp(ast.And(), [ast.Num(4), None]) + b = ast.BoolOp(ast.And(), [ast.Constant(4), None]) self.expr(b, "None disallowed") - b = ast.BoolOp(ast.And(), [ast.Num(4), ast.Name("x", ast.Store())]) + b = ast.BoolOp(ast.And(), [ast.Constant(4), ast.Name("x", ast.Store())]) self.expr(b, "must have Load context") def test_unaryop(self): @@ -1451,11 +1635,11 @@ def test_compare(self): left = ast.Name("x", ast.Load()) comp = ast.Compare(left, [ast.In()], []) self.expr(comp, "no comparators") - comp = ast.Compare(left, [ast.In()], [ast.Num(4), ast.Num(5)]) + comp = ast.Compare(left, [ast.In()], [ast.Constant(4), ast.Constant(5)]) self.expr(comp, "different number of comparators and operands") - comp = ast.Compare(ast.Num("blah"), [ast.In()], [left]) + comp = ast.Compare(ast.Constant("blah"), [ast.In()], [left]) self.expr(comp) - comp = ast.Compare(left, [ast.In()], [ast.Num("blah")]) + comp = ast.Compare(left, [ast.In()], [ast.Constant("blah")]) self.expr(comp) def test_call(self): @@ -1471,23 +1655,37 @@ def test_call(self): self.expr(call, "must have Load context") def test_num(self): - class subint(int): - pass - class subfloat(float): - pass - class subcomplex(complex): - pass - for obj in "0", "hello": - self.expr(ast.Num(obj)) - for obj in subint(), subfloat(), subcomplex(): - self.expr(ast.Num(obj), "invalid type", exc=TypeError) + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import Num + + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('always', '', DeprecationWarning) + class subint(int): + pass + class subfloat(float): + pass + class subcomplex(complex): + pass + for obj in "0", "hello": + self.expr(ast.Num(obj)) + for obj in subint(), subfloat(), subcomplex(): + self.expr(ast.Num(obj), "invalid type", exc=TypeError) + + self.assertEqual([str(w.message) for w in wlog], [ + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + ]) def test_attribute(self): attr = ast.Attribute(ast.Name("x", ast.Store()), "y", ast.Load()) self.expr(attr, "must have Load context") def test_subscript(self): - sub = ast.Subscript(ast.Name("x", ast.Store()), ast.Num(3), + sub = ast.Subscript(ast.Name("x", ast.Store()), ast.Constant(3), ast.Load()) self.expr(sub, "must have Load context") x = ast.Name("x", ast.Load()) @@ -1507,7 +1705,7 @@ def test_subscript(self): def test_starred(self): left = ast.List([ast.Starred(ast.Name("x", ast.Load()), ast.Store())], ast.Store()) - assign = ast.Assign([left], ast.Num(4)) + assign = ast.Assign([left], ast.Constant(4)) self.stmt(assign, "must have Store context") def _sequence(self, fac): @@ -1522,7 +1720,17 @@ def test_tuple(self): self._sequence(ast.Tuple) def test_nameconstant(self): - self.expr(ast.NameConstant(4)) + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import NameConstant + + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('always', '', DeprecationWarning) + self.expr(ast.NameConstant(4)) + + self.assertEqual([str(w.message) for w in wlog], [ + 'ast.NameConstant is deprecated; use ast.Constant instead', + ]) def test_stdlib_validates(self): stdlib = os.path.dirname(ast.__file__) @@ -2199,10 +2407,15 @@ def visit_Ellipsis(self, node): ]) self.assertEqual([str(w.message) for w in wlog], [ 'visit_Num is deprecated; add visit_Constant', + 'Attribute n is deprecated; use value instead', 'visit_Num is deprecated; add visit_Constant', + 'Attribute n is deprecated; use value instead', 'visit_Num is deprecated; add visit_Constant', + 'Attribute n is deprecated; use value instead', 'visit_Str is deprecated; add visit_Constant', + 'Attribute s is deprecated; use value instead', 'visit_Bytes is deprecated; add visit_Constant', + 'Attribute s is deprecated; use value instead', 'visit_NameConstant is deprecated; add visit_Constant', 'visit_NameConstant is deprecated; add visit_Constant', 'visit_Ellipsis is deprecated; add visit_Constant', diff --git a/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst new file mode 100644 index 00000000000000..48675dfd477ed8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst @@ -0,0 +1,2 @@ +Deprecation warnings are now emitted for deprecated features in the +:mod:`ast` module which were deprecated in documentation only. From fdefe87709bd774b9d02ac584cd52884a5da9ecb Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 4 May 2023 13:12:59 +0100 Subject: [PATCH 02/20] fix patchcheck Co-authored-by: Hugo van Kemenade --- Lib/test/test_ast.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 9fb0fa1e83d053..03f7f5c5e32270 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -468,8 +468,8 @@ def test_field_attr_existence_deprecated(self): # The argument is required. continue with self.subTest(item): - with self.assertWarns(DeprecationWarning): - x = item() + with self.assertWarns(DeprecationWarning): + x = item() if isinstance(x, ast.AST): self.assertEqual(type(x._fields), tuple) From 60ab0c400f1ee88d498c1d1a146742557f7c02e0 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Thu, 4 May 2023 15:20:33 +0100 Subject: [PATCH 03/20] Remove `DeprecationWarning`s for `Index` and `ExtSlice` --- Lib/ast.py | 5 ----- Lib/test/test_ast.py | 20 +++++++------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 2bb62d561eda5a..826739554a324b 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -627,9 +627,6 @@ def __new__(cls, value, **kwargs): class ExtSlice(slice): """Deprecated AST node class. Use ast.Tuple instead.""" def __new__(cls, dims=(), **kwargs): - import warnings - warnings.warn("ast.ExtSlice is deprecated; use ast.Tuple instead", - DeprecationWarning, stacklevel=2) return Tuple(list(dims), Load(), **kwargs) # If the ast module is loaded more than once, only add deprecated methods once @@ -1737,8 +1734,6 @@ def unparse(ast_obj): for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis') } _deprecated_globals['slice'] = globals().pop('slice'), '' -_deprecated_globals['Index'] = globals().pop('Index'), '' -_deprecated_globals['ExtSlice'] = globals().pop('ExtSlice'), '; use ast.Tuple instead' def __getattr__(name): if name in _deprecated_globals: diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 03f7f5c5e32270..e452b3d94bef59 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -446,27 +446,17 @@ def test_import_deprecated(self): with self.assertWarnsRegex(DeprecationWarning, r'ast\.slice is deprecated'): ast.slice - with self.assertWarnsRegex(DeprecationWarning, - r'ast\.Index is deprecated'): - ast.Index - with self.assertWarnsRegex(DeprecationWarning, - r'ast\.ExtSlice is deprecated; use ast\.Tuple instead'): - ast.ExtSlice def test_field_attr_existence_deprecated(self): with warnings.catch_warnings(): warnings.filterwarnings('ignore', '', DeprecationWarning) from ast import Num, Str, Bytes, NameConstant, Ellipsis - from ast import slice, Index, ExtSlice + from ast import slice for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis', - 'slice', 'Index', 'ExtSlice'): + 'slice'): item = getattr(ast, name) if self._is_ast_node(name, item): - if name == 'Index': - # Index(value) just returns value now. - # The argument is required. - continue with self.subTest(item): with self.assertWarns(DeprecationWarning): x = item() @@ -475,8 +465,12 @@ def test_field_attr_existence_deprecated(self): def test_field_attr_existence(self): for name, item in ast.__dict__.items(): + # These emit DeprecationWarnings if name in {'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis', - 'slice', 'Index', 'ExtSlice'}: + 'slice'}: + continue + # constructor has a different signature + if name == 'Index': continue if self._is_ast_node(name, item): x = item() From 951c5487f575fb52eac7bc569ec6d59a0abeabdd Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 12:48:43 +0100 Subject: [PATCH 04/20] Also emit DeprecationWarning during `isinstance()` checks --- Lib/ast.py | 4 + Lib/test/test_ast.py | 183 ++++++++++++++++++++++++++++--------------- 2 files changed, 122 insertions(+), 65 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 826739554a324b..9cb1928b009365 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -537,6 +537,10 @@ def __init__(cls, *args): cls.__doc__ = """Deprecated AST node class. Use ast.Constant instead""" def __instancecheck__(cls, inst): + if cls in _const_types: + import warnings + warnings.warn(f"ast.{cls.__qualname__} is deprecated; use ast.Constant instead", + DeprecationWarning, stacklevel=2) if not isinstance(inst, Constant): return False if cls in _const_types: diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index e452b3d94bef59..e979d58bf009e2 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -8,6 +8,7 @@ import unittest import warnings import weakref +from functools import partial from textwrap import dedent from test import support @@ -656,76 +657,127 @@ def test_realtype(self): ]) def test_isinstance(self): + from ast import Constant + with warnings.catch_warnings(): warnings.filterwarnings('ignore', '', DeprecationWarning) from ast import Num, Str, Bytes, NameConstant, Ellipsis - with warnings.catch_warnings(record=True) as wlog: - warnings.filterwarnings('always', '', DeprecationWarning) - self.assertTrue(isinstance(ast.Num(42), ast.Num)) - self.assertTrue(isinstance(ast.Num(4.2), ast.Num)) - self.assertTrue(isinstance(ast.Num(4.2j), ast.Num)) - self.assertTrue(isinstance(ast.Str('42'), ast.Str)) - self.assertTrue(isinstance(ast.Bytes(b'42'), ast.Bytes)) - self.assertTrue(isinstance(ast.NameConstant(True), ast.NameConstant)) - self.assertTrue(isinstance(ast.NameConstant(False), ast.NameConstant)) - self.assertTrue(isinstance(ast.NameConstant(None), ast.NameConstant)) - self.assertTrue(isinstance(ast.Ellipsis(), ast.Ellipsis)) - - self.assertTrue(isinstance(ast.Constant(42), ast.Num)) - self.assertTrue(isinstance(ast.Constant(4.2), ast.Num)) - self.assertTrue(isinstance(ast.Constant(4.2j), ast.Num)) - self.assertTrue(isinstance(ast.Constant('42'), ast.Str)) - self.assertTrue(isinstance(ast.Constant(b'42'), ast.Bytes)) - self.assertTrue(isinstance(ast.Constant(True), ast.NameConstant)) - self.assertTrue(isinstance(ast.Constant(False), ast.NameConstant)) - self.assertTrue(isinstance(ast.Constant(None), ast.NameConstant)) - self.assertTrue(isinstance(ast.Constant(...), ast.Ellipsis)) - - self.assertFalse(isinstance(ast.Str('42'), ast.Num)) - self.assertFalse(isinstance(ast.Num(42), ast.Str)) - self.assertFalse(isinstance(ast.Str('42'), ast.Bytes)) - self.assertFalse(isinstance(ast.Num(42), ast.NameConstant)) - self.assertFalse(isinstance(ast.Num(42), ast.Ellipsis)) - self.assertFalse(isinstance(ast.NameConstant(True), ast.Num)) - self.assertFalse(isinstance(ast.NameConstant(False), ast.Num)) - - self.assertFalse(isinstance(ast.Constant('42'), ast.Num)) - self.assertFalse(isinstance(ast.Constant(42), ast.Str)) - self.assertFalse(isinstance(ast.Constant('42'), ast.Bytes)) - self.assertFalse(isinstance(ast.Constant(42), ast.NameConstant)) - self.assertFalse(isinstance(ast.Constant(42), ast.Ellipsis)) - self.assertFalse(isinstance(ast.Constant(True), ast.Num)) - self.assertFalse(isinstance(ast.Constant(False), ast.Num)) - - self.assertFalse(isinstance(ast.Constant(), ast.Num)) - self.assertFalse(isinstance(ast.Constant(), ast.Str)) - self.assertFalse(isinstance(ast.Constant(), ast.Bytes)) - self.assertFalse(isinstance(ast.Constant(), ast.NameConstant)) - self.assertFalse(isinstance(ast.Constant(), ast.Ellipsis)) - - class S(str): pass - self.assertTrue(isinstance(ast.Constant(S('42')), ast.Str)) - self.assertFalse(isinstance(ast.Constant(S('42')), ast.Num)) + assertNumDeprecated = partial( + self.assertWarnsRegex, + DeprecationWarning, + 'ast.Num is deprecated; use ast.Constant instead' + ) + assertStrDeprecated = partial( + self.assertWarnsRegex, + DeprecationWarning, + 'ast.Str is deprecated; use ast.Constant instead' + ) + assertBytesDeprecated = partial( + self.assertWarnsRegex, + DeprecationWarning, + 'ast.Bytes is deprecated; use ast.Constant instead' + ) + assertNameConstantDeprecated = partial( + self.assertWarnsRegex, + DeprecationWarning, + 'ast.NameConstant is deprecated; use ast.Constant instead' + ) + assertEllipsisDeprecated = partial( + self.assertWarnsRegex, + DeprecationWarning, + 'ast.Ellipsis is deprecated; use ast.Constant instead' + ) - self.assertEqual([str(w.message) for w in wlog], [ - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Str is deprecated; use ast.Constant instead', - 'ast.Bytes is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.Ellipsis is deprecated; use ast.Constant instead', - 'ast.Str is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Str is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - ]) + for arg in 42, 4.2, 4.2j: + with self.subTest(arg=arg): + with assertNumDeprecated(): + n = Num(arg) + with assertNumDeprecated(): + self.assertIsInstance(n, Num) + + with assertStrDeprecated(): + s = Str('42') + with assertStrDeprecated(): + self.assertIsInstance(s, Str) + + with assertBytesDeprecated(): + b = Bytes(b'42') + with assertBytesDeprecated(): + self.assertIsInstance(b, Bytes) + + for arg in True, False, None: + with self.subTest(arg=arg): + with assertNameConstantDeprecated(): + n = NameConstant(arg) + with assertNameConstantDeprecated(): + self.assertIsInstance(n, NameConstant) + + with assertEllipsisDeprecated(): + e = Ellipsis() + with assertEllipsisDeprecated(): + self.assertIsInstance(e, Ellipsis) + + for arg in 42, 4.2, 4.2j: + with self.subTest(arg=arg): + with assertNumDeprecated(): + self.assertIsInstance(Constant(arg), Num) + + with assertStrDeprecated(): + self.assertIsInstance(Constant('42'), Str) + + with assertBytesDeprecated(): + self.assertIsInstance(Constant(b'42'), Bytes) + + for arg in True, False, None: + with self.subTest(arg=arg): + with assertNameConstantDeprecated(): + self.assertIsInstance(Constant(arg), NameConstant) + + with assertEllipsisDeprecated(): + self.assertIsInstance(Constant(...), Ellipsis) + + with assertStrDeprecated(): + s = Str('42') + assertNumDeprecated(self.assertNotIsInstance, s, Num) + assertBytesDeprecated(self.assertNotIsInstance, s, Bytes) + + with assertNumDeprecated(): + n = Num(42) + assertStrDeprecated(self.assertNotIsInstance, n, Str) + assertNameConstantDeprecated(self.assertNotIsInstance, n, NameConstant) + assertEllipsisDeprecated(self.assertNotIsInstance, n, Ellipsis) + + with assertNameConstantDeprecated(): + n = NameConstant(True) + with assertNumDeprecated(): + self.assertNotIsInstance(n, Num) + + with assertNameConstantDeprecated(): + n = NameConstant(False) + with assertNumDeprecated(): + self.assertNotIsInstance(n, Num) + + for arg in '42', True, False: + with self.subTest(arg=arg): + with assertNumDeprecated(): + self.assertNotIsInstance(Constant(arg), Num) + + assertStrDeprecated(self.assertNotIsInstance, Constant(42), Str) + assertBytesDeprecated(self.assertNotIsInstance, Constant('42'), Bytes) + assertNameConstantDeprecated(self.assertNotIsInstance, Constant(42), NameConstant) + assertEllipsisDeprecated(self.assertNotIsInstance, Constant(42), Ellipsis) + assertNumDeprecated(self.assertNotIsInstance, Constant(), Num) + assertStrDeprecated(self.assertNotIsInstance, Constant(), Str) + assertBytesDeprecated(self.assertNotIsInstance, Constant(), Bytes) + assertNameConstantDeprecated(self.assertNotIsInstance, Constant(), NameConstant) + assertEllipsisDeprecated(self.assertNotIsInstance, Constant(), Ellipsis) + + class S(str): pass + with assertStrDeprecated(): + self.assertTrue(isinstance(Constant(S('42')), Str)) + with assertNumDeprecated(): + self.assertFalse(isinstance(Constant(S('42')), Num)) def test_constant_subclasses_deprecated(self): with warnings.catch_warnings(): @@ -757,6 +809,7 @@ class N2(ast.Num): 'Attribute n is deprecated; use value instead', 'Attribute n is deprecated; use value instead', 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', 'Attribute n is deprecated; use value instead', 'Attribute n is deprecated; use value instead', ]) From 9025b7e8c2f21ba60e7aeb8ba17aa34b356af3d7 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 12:56:42 +0100 Subject: [PATCH 05/20] Add whatsnew --- Doc/whatsnew/3.12.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index edbf921467553c..a0728e9d7ea2fc 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -835,6 +835,19 @@ although there is currently no date scheduled for their removal. :keyword:`for`, :keyword:`if`, :keyword:`in`, :keyword:`is` and :keyword:`or`. In a future release it will be changed to a syntax error. (:gh:`87999`) +* The following :mod:`ast` features have been deprecated in documentation since + Python 3.8, now cause a :exc:`DeprecationWarning` to be emitted at runtime + when they are accessed or used, and may be removed in a future version of Python: + + * :class:`!ast.Num` + * :class:`!ast.Str` + * :class:`!ast.Bytes` + * :class:`!ast.NameConstant` + * :class:`!ast.Ellipsis` + + Use :class:`ast.Constant` instead. + (Contributed by Serhiy Storchaka in :gh:`90953`.) + Removed ======= From 3d2de749eabc8d14b67f020c1b20804c744d8f8e Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 12:59:04 +0100 Subject: [PATCH 06/20] Tweak news entry --- .../next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst index 48675dfd477ed8..16847809855a78 100644 --- a/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst +++ b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst @@ -1,2 +1,4 @@ -Deprecation warnings are now emitted for deprecated features in the -:mod:`ast` module which were deprecated in documentation only. +Deprecation warnings are now emitted for :class:`!ast.Num`, +:class:`!ast.Bytes`, :class:`!ast.Str`, :class:`!ast.NameConstant` and +:class:`!ast.Ellipsis`. These have been documented as deprecated since Python +3.8, and may be removed in a future version of Python. From 6e9f5f5fb0fc4c89bdc55ccd317e1852882b750e Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 5 May 2023 13:48:04 +0100 Subject: [PATCH 07/20] Apply suggestions from code review Co-authored-by: Hugo van Kemenade --- Doc/whatsnew/3.12.rst | 2 +- .../next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 6a696988013df7..16b7a6ba1d4e33 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -858,7 +858,7 @@ although there is currently no date scheduled for their removal. * The following :mod:`ast` features have been deprecated in documentation since Python 3.8, now cause a :exc:`DeprecationWarning` to be emitted at runtime - when they are accessed or used, and may be removed in a future version of Python: + when they are accessed or used, and will be removed in Python 3.14. * :class:`!ast.Num` * :class:`!ast.Str` diff --git a/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst index 16847809855a78..6539efbc9d0eb0 100644 --- a/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst +++ b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst @@ -1,4 +1,4 @@ Deprecation warnings are now emitted for :class:`!ast.Num`, :class:`!ast.Bytes`, :class:`!ast.Str`, :class:`!ast.NameConstant` and :class:`!ast.Ellipsis`. These have been documented as deprecated since Python -3.8, and may be removed in a future version of Python. +3.8, and will be removed in Python 3.14. From 8f86b9a1e88c863253d09537edd64a9014a078ae Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 14:05:09 +0100 Subject: [PATCH 08/20] Use `warnings._deprecated --- Lib/ast.py | 51 +++++++++++------ Lib/test/test_ast.py | 128 +++++++++++++++++++++---------------------- 2 files changed, 99 insertions(+), 80 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 9cb1928b009365..8745cc8b94a71b 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -497,6 +497,15 @@ def generic_visit(self, node): return node +_DEPRECATED_VALUE_ALIAS_MESSAGE = ( + "{name} is deprecated and will be removed in Python {remove}; use value instead" +) +_DEPRECATED_CLASS_MESSAGE = ( + "{name} is deprecated and will be removed in Python {remove}; " + "use ast.Constant instead" +) + + # If the ast module is loaded more than once, only add deprecated methods once if not hasattr(Constant, 'n'): # The following code is for backward compatibility. @@ -505,27 +514,31 @@ def generic_visit(self, node): def _n_getter(self): """Deprecated. Use value instead.""" import warnings - warnings.warn("Attribute n is deprecated; use value instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + "Attribute n", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14) + ) return self.value def _n_setter(self, value): import warnings - warnings.warn("Attribute n is deprecated; use value instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + "Attribute n", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14) + ) self.value = value def _s_getter(self): """Deprecated. Use value instead.""" import warnings - warnings.warn("Attribute s is deprecated; use value instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + "Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14) + ) return self.value def _s_setter(self, value): import warnings - warnings.warn("Attribute s is deprecated; use value instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + "Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14) + ) self.value = value Constant.n = property(_n_getter, _n_setter) @@ -539,8 +552,11 @@ def __init__(cls, *args): def __instancecheck__(cls, inst): if cls in _const_types: import warnings - warnings.warn(f"ast.{cls.__qualname__} is deprecated; use ast.Constant instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + f"ast.{cls.__qualname__}", + message=_DEPRECATED_CLASS_MESSAGE, + remove=(3, 14) + ) if not isinstance(inst, Constant): return False if cls in _const_types: @@ -565,8 +581,9 @@ def _new(cls, *args, **kwargs): raise TypeError(f"{cls.__name__} got multiple values for argument {key!r}") if cls in _const_types: import warnings - warnings.warn(f"ast.{cls.__qualname__} is deprecated; use ast.Constant instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + f"ast.{cls.__qualname__}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14) + ) return Constant(*args, **kwargs) return Constant.__new__(cls, *args, **kwargs) @@ -591,8 +608,9 @@ class Ellipsis(Constant, metaclass=_ABC): def __new__(cls, *args, **kwargs): if cls is _ast_Ellipsis: import warnings - warnings.warn("ast.Ellipsis is deprecated; use ast.Constant instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + f"ast.Ellipsis", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14) + ) return Constant(..., *args, **kwargs) return Constant.__new__(cls, *args, **kwargs) @@ -1744,8 +1762,9 @@ def __getattr__(name): value, details = _deprecated_globals[name] globals()[name] = value import warnings - warnings.warn(f"ast.{name} is deprecated{details}", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + f"ast.{name}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14) + ) return value raise AttributeError(f"module 'ast' has no attribute '{name}'") diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index e979d58bf009e2..fa701ce804e46e 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -440,9 +440,12 @@ def test_base_classes(self): def test_import_deprecated(self): ast = import_fresh_module('ast') + depr_regex = ( + r'ast\.{} is deprecated and will be removed in Python 3.14; ' + r'use ast\.Constant instead' + ) for name in 'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis': - with self.assertWarnsRegex(DeprecationWarning, - fr'ast\.{name} is deprecated; use ast\.Constant instead'): + with self.assertWarnsRegex(DeprecationWarning, depr_regex.format(name)): getattr(ast, name) with self.assertWarnsRegex(DeprecationWarning, r'ast\.slice is deprecated'): @@ -559,29 +562,29 @@ def test_classattrs_deprecated(self): self.assertIs(ast.NameConstant(None).value, None) self.assertEqual([str(w.message) for w in wlog], [ - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'ast.Str is deprecated; use ast.Constant instead', - 'Attribute s is deprecated; use value instead', - 'ast.Bytes is deprecated; use ast.Constant instead', - 'Attribute s is deprecated; use value instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Str is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute s is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Bytes is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute s is deprecated and will be removed in Python 3.14; use value instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', ]) def test_classattrs(self): @@ -645,15 +648,15 @@ def test_realtype(self): self.assertEqual(type(ast.Ellipsis()), ast.Constant) self.assertEqual([str(w.message) for w in wlog], [ - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Str is deprecated; use ast.Constant instead', - 'ast.Bytes is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.Ellipsis is deprecated; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Str is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Bytes is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Ellipsis is deprecated and will be removed in Python 3.14; use ast.Constant instead', ]) def test_isinstance(self): @@ -663,30 +666,27 @@ def test_isinstance(self): warnings.filterwarnings('ignore', '', DeprecationWarning) from ast import Num, Str, Bytes, NameConstant, Ellipsis + cls_depr_msg = ( + 'ast.{} is deprecated and will be removed in Python 3.14; ' + 'use ast.Constant instead' + ) + assertNumDeprecated = partial( - self.assertWarnsRegex, - DeprecationWarning, - 'ast.Num is deprecated; use ast.Constant instead' + self.assertWarnsRegex, DeprecationWarning, cls_depr_msg.format("Num") ) assertStrDeprecated = partial( - self.assertWarnsRegex, - DeprecationWarning, - 'ast.Str is deprecated; use ast.Constant instead' + self.assertWarnsRegex, DeprecationWarning, cls_depr_msg.format("Str") ) assertBytesDeprecated = partial( - self.assertWarnsRegex, - DeprecationWarning, - 'ast.Bytes is deprecated; use ast.Constant instead' + self.assertWarnsRegex, DeprecationWarning, cls_depr_msg.format("Bytes") ) assertNameConstantDeprecated = partial( self.assertWarnsRegex, DeprecationWarning, - 'ast.NameConstant is deprecated; use ast.Constant instead' + cls_depr_msg.format("NameConstant") ) assertEllipsisDeprecated = partial( - self.assertWarnsRegex, - DeprecationWarning, - 'ast.Ellipsis is deprecated; use ast.Constant instead' + self.assertWarnsRegex, DeprecationWarning, cls_depr_msg.format("Ellipsis") ) for arg in 42, 4.2, 4.2j: @@ -806,12 +806,12 @@ class N2(ast.Num): self.assertEqual(type(n), N) self.assertEqual([str(w.message) for w in wlog], [ - 'Attribute n is deprecated; use value instead', - 'Attribute n is deprecated; use value instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'Attribute n is deprecated; use value instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', ]) def test_constant_subclasses(self): @@ -1866,11 +1866,11 @@ class subcomplex(complex): self.expr(ast.Num(obj), "invalid type", exc=TypeError) self.assertEqual([str(w.message) for w in wlog], [ - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', ]) def test_attribute(self): @@ -1922,7 +1922,7 @@ def test_nameconstant(self): self.expr(ast.NameConstant(4)) self.assertEqual([str(w.message) for w in wlog], [ - 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', ]) def test_stdlib_validates(self): @@ -2612,15 +2612,15 @@ def visit_Ellipsis(self, node): ]) self.assertEqual([str(w.message) for w in wlog], [ 'visit_Num is deprecated; add visit_Constant', - 'Attribute n is deprecated; use value instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'visit_Num is deprecated; add visit_Constant', - 'Attribute n is deprecated; use value instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'visit_Num is deprecated; add visit_Constant', - 'Attribute n is deprecated; use value instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'visit_Str is deprecated; add visit_Constant', - 'Attribute s is deprecated; use value instead', + 'Attribute s is deprecated and will be removed in Python 3.14; use value instead', 'visit_Bytes is deprecated; add visit_Constant', - 'Attribute s is deprecated; use value instead', + 'Attribute s is deprecated and will be removed in Python 3.14; use value instead', 'visit_NameConstant is deprecated; add visit_Constant', 'visit_NameConstant is deprecated; add visit_Constant', 'visit_Ellipsis is deprecated; add visit_Constant', From 8c949f37a9650273c9933ae5ad4190cabeabcebf Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 14:07:02 +0100 Subject: [PATCH 09/20] Move whatsnew entry to correct location --- Doc/whatsnew/3.12.rst | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 16b7a6ba1d4e33..eca499393f165b 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -839,6 +839,19 @@ Pending Removal in Python 3.14 use :func:`importlib.util.find_spec` instead. (Contributed by Nikita Sobolev in :gh:`97850`.) +* The following :mod:`ast` features have been deprecated in documentation since + Python 3.8, now cause a :exc:`DeprecationWarning` to be emitted at runtime + when they are accessed or used, and will be removed in Python 3.14. + + * :class:`!ast.Num` + * :class:`!ast.Str` + * :class:`!ast.Bytes` + * :class:`!ast.NameConstant` + * :class:`!ast.Ellipsis` + + Use :class:`ast.Constant` instead. + (Contributed by Serhiy Storchaka in :gh:`90953`.) + Pending Removal in Future Versions ---------------------------------- @@ -856,20 +869,6 @@ although there is currently no date scheduled for their removal. :keyword:`for`, :keyword:`if`, :keyword:`in`, :keyword:`is` and :keyword:`or`. In a future release it will be changed to a syntax error. (:gh:`87999`) -* The following :mod:`ast` features have been deprecated in documentation since - Python 3.8, now cause a :exc:`DeprecationWarning` to be emitted at runtime - when they are accessed or used, and will be removed in Python 3.14. - - * :class:`!ast.Num` - * :class:`!ast.Str` - * :class:`!ast.Bytes` - * :class:`!ast.NameConstant` - * :class:`!ast.Ellipsis` - - Use :class:`ast.Constant` instead. - (Contributed by Serhiy Storchaka in :gh:`90953`.) - - Removed ======= From 3d71e66cb4321b68ab822c631d72855298e1dd80 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 14:19:28 +0100 Subject: [PATCH 10/20] Revert spurious whitespace change --- Doc/whatsnew/3.12.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index eca499393f165b..b92dfbe07b0a0c 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -869,6 +869,7 @@ although there is currently no date scheduled for their removal. :keyword:`for`, :keyword:`if`, :keyword:`in`, :keyword:`is` and :keyword:`or`. In a future release it will be changed to a syntax error. (:gh:`87999`) + Removed ======= From 4261cc6551fea02f50dc33c4a406cc5e149c1683 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 14:26:59 +0100 Subject: [PATCH 11/20] Revert warning for `dims` attribute (only deprecated in 3.9) --- Lib/ast.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 8745cc8b94a71b..ce0c7e0c054141 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -658,14 +658,9 @@ def __new__(cls, dims=(), **kwargs): def _dims_getter(self): """Deprecated. Use elts instead.""" - import warnings - warnings.warn("Attribute dims is deprecated; use elts instead", - DeprecationWarning, stacklevel=2) return self.elts def _dims_setter(self, value): - warnings.warn("Attribute dims is deprecated, use elts instead", - DeprecationWarning, stacklevel=2) self.elts = value Tuple.dims = property(_dims_getter, _dims_setter) From a785a0c22c4c60d36b44588e63a152a564dab87a Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 15:23:08 +0100 Subject: [PATCH 12/20] Also remove deprecation for `ast.slice`, deprecated in 3.9 --- Lib/ast.py | 1 - Lib/test/test_ast.py | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index ce0c7e0c054141..494d4cac2238ba 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -1750,7 +1750,6 @@ def unparse(ast_obj): name: (globals().pop(name), '; use ast.Constant instead') for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis') } -_deprecated_globals['slice'] = globals().pop('slice'), '' def __getattr__(name): if name in _deprecated_globals: diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index fa701ce804e46e..84270328beaf6c 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -447,18 +447,13 @@ def test_import_deprecated(self): for name in 'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis': with self.assertWarnsRegex(DeprecationWarning, depr_regex.format(name)): getattr(ast, name) - with self.assertWarnsRegex(DeprecationWarning, - r'ast\.slice is deprecated'): - ast.slice def test_field_attr_existence_deprecated(self): with warnings.catch_warnings(): warnings.filterwarnings('ignore', '', DeprecationWarning) from ast import Num, Str, Bytes, NameConstant, Ellipsis - from ast import slice - for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis', - 'slice'): + for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis'): item = getattr(ast, name) if self._is_ast_node(name, item): with self.subTest(item): @@ -470,8 +465,7 @@ def test_field_attr_existence_deprecated(self): def test_field_attr_existence(self): for name, item in ast.__dict__.items(): # These emit DeprecationWarnings - if name in {'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis', - 'slice'}: + if name in {'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis'}: continue # constructor has a different signature if name == 'Index': From a3491eab5e5b8c0dbc2fff4c58e99626f1ba2ed9 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 15:35:18 +0100 Subject: [PATCH 13/20] Better unittest style --- Lib/test/test_ast.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 84270328beaf6c..a49c34d608c78f 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -769,9 +769,9 @@ def test_isinstance(self): class S(str): pass with assertStrDeprecated(): - self.assertTrue(isinstance(Constant(S('42')), Str)) + self.assertIsInstance(Constant(S('42')), Str) with assertNumDeprecated(): - self.assertFalse(isinstance(Constant(S('42')), Num)) + self.assertNotIsInstance(Constant(S('42')), Num) def test_constant_subclasses_deprecated(self): with warnings.catch_warnings(): @@ -790,14 +790,14 @@ class N2(ast.Num): n = N(42) self.assertEqual(n.n, 42) self.assertEqual(n.z, 'spam') - self.assertEqual(type(n), N) - self.assertTrue(isinstance(n, N)) - self.assertTrue(isinstance(n, ast.Num)) - self.assertFalse(isinstance(n, N2)) - self.assertFalse(isinstance(ast.Num(42), N)) + self.assertIs(type(n), N) + self.assertIsInstance(n, N) + self.assertIsinstance(n, ast.Num) + self.assertNotIsInstance(n, N2) + self.assertNotIsInstance(ast.Num(42), N) n = N(n=42) self.assertEqual(n.n, 42) - self.assertEqual(type(n), N) + self.assertIs(type(n), N) self.assertEqual([str(w.message) for w in wlog], [ 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', From 3613908df40549c493dd56fca9dfcb62e42dd5ae Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 15:39:56 +0100 Subject: [PATCH 14/20] . --- Lib/test/test_ast.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index a49c34d608c78f..4126d9140c860c 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -631,15 +631,15 @@ def test_realtype(self): with warnings.catch_warnings(record=True) as wlog: warnings.filterwarnings('always', '', DeprecationWarning) - self.assertEqual(type(ast.Num(42)), ast.Constant) - self.assertEqual(type(ast.Num(4.25)), ast.Constant) - self.assertEqual(type(ast.Num(4.25j)), ast.Constant) - self.assertEqual(type(ast.Str('42')), ast.Constant) - self.assertEqual(type(ast.Bytes(b'42')), ast.Constant) - self.assertEqual(type(ast.NameConstant(True)), ast.Constant) - self.assertEqual(type(ast.NameConstant(False)), ast.Constant) - self.assertEqual(type(ast.NameConstant(None)), ast.Constant) - self.assertEqual(type(ast.Ellipsis()), ast.Constant) + self.assertIs(type(ast.Num(42)), ast.Constant) + self.assertIs(type(ast.Num(4.25)), ast.Constant) + self.assertIs(type(ast.Num(4.25j)), ast.Constant) + self.assertIs(type(ast.Str('42')), ast.Constant) + self.assertIs(type(ast.Bytes(b'42')), ast.Constant) + self.assertIs(type(ast.NameConstant(True)), ast.Constant) + self.assertIs(type(ast.NameConstant(False)), ast.Constant) + self.assertIs(type(ast.NameConstant(None)), ast.Constant) + self.assertIs(type(ast.Ellipsis()), ast.Constant) self.assertEqual([str(w.message) for w in wlog], [ 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', From 9bba5dd5d4332997e559003bcb35067936c73531 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 5 May 2023 15:49:42 +0100 Subject: [PATCH 15/20] Update 3.12.rst --- Doc/whatsnew/3.12.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index b92dfbe07b0a0c..32bfd5a106cdd4 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -841,7 +841,7 @@ Pending Removal in Python 3.14 * The following :mod:`ast` features have been deprecated in documentation since Python 3.8, now cause a :exc:`DeprecationWarning` to be emitted at runtime - when they are accessed or used, and will be removed in Python 3.14. + when they are accessed or used, and will be removed in Python 3.14: * :class:`!ast.Num` * :class:`!ast.Str` From 234243a3f26aa0790e29405c1a73f1c8471b4c19 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 16:18:26 +0100 Subject: [PATCH 16/20] typo --- Lib/test/test_ast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 4126d9140c860c..810200f6306393 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -792,7 +792,7 @@ class N2(ast.Num): self.assertEqual(n.z, 'spam') self.assertIs(type(n), N) self.assertIsInstance(n, N) - self.assertIsinstance(n, ast.Num) + self.assertIsInstance(n, ast.Num) self.assertNotIsInstance(n, N2) self.assertNotIsInstance(ast.Num(42), N) n = N(n=42) From 915e12e363c3e6891ce9882be2bce7c7bb2cec8c Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sat, 6 May 2023 15:09:52 +0100 Subject: [PATCH 17/20] Add comment explaining subtle code --- Lib/ast.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/ast.py b/Lib/ast.py index 494d4cac2238ba..4b876ea4404614 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -614,6 +614,9 @@ def __new__(cls, *args, **kwargs): return Constant(..., *args, **kwargs) return Constant.__new__(cls, *args, **kwargs) +# Keep another reference to Ellipsis in the global namespace +# so it can be referenced in Ellipsis.__new__ +# (The original "Ellipsis" name is removed from the global namespace later on) _ast_Ellipsis = Ellipsis _const_types = { From a7abed267655bcaad41ad14a16ab33ee93f7e1dc Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sat, 6 May 2023 16:58:28 +0100 Subject: [PATCH 18/20] Simplify slightly --- Lib/ast.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 4b876ea4404614..a1870075a8164f 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -1750,14 +1750,13 @@ def unparse(ast_obj): _deprecated_globals = { - name: (globals().pop(name), '; use ast.Constant instead') + name: globals().pop(name) for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis') } def __getattr__(name): if name in _deprecated_globals: - value, details = _deprecated_globals[name] - globals()[name] = value + globals()[name] = value = _deprecated_globals[name] import warnings warnings._deprecated( f"ast.{name}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14) From 7ceb9ccf9a10a2c7f405a516f938843996bda259 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sat, 6 May 2023 17:12:26 +0100 Subject: [PATCH 19/20] Unnecessary f-string --- Lib/ast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ast.py b/Lib/ast.py index a1870075a8164f..65152047a22370 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -609,7 +609,7 @@ def __new__(cls, *args, **kwargs): if cls is _ast_Ellipsis: import warnings warnings._deprecated( - f"ast.Ellipsis", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14) + "ast.Ellipsis", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14) ) return Constant(..., *args, **kwargs) return Constant.__new__(cls, *args, **kwargs) From ecf092fdc290d63676020c504f7a1303fe9f1a9d Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sat, 6 May 2023 17:18:47 +0100 Subject: [PATCH 20/20] One last nit --- Lib/test/test_ast.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 810200f6306393..fdd21aca06ffdd 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -460,7 +460,7 @@ def test_field_attr_existence_deprecated(self): with self.assertWarns(DeprecationWarning): x = item() if isinstance(x, ast.AST): - self.assertEqual(type(x._fields), tuple) + self.assertIs(type(x._fields), tuple) def test_field_attr_existence(self): for name, item in ast.__dict__.items(): @@ -473,7 +473,7 @@ def test_field_attr_existence(self): if self._is_ast_node(name, item): x = item() if isinstance(x, ast.AST): - self.assertEqual(type(x._fields), tuple) + self.assertIs(type(x._fields), tuple) def test_arguments(self): x = ast.arguments() pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy