Skip to content

Commit 376137f

Browse files
gh-90953: Emit deprecation warnings for ast features deprecated in Python 3.8 (#104199)
`ast.Num`, `ast.Str`, `ast.Bytes`, `ast.Ellipsis` and `ast.NameConstant` now all emit deprecation warnings on import, access, instantation or `isinstance()` checks. Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 263abd3 commit 376137f

File tree

4 files changed

+472
-135
lines changed

4 files changed

+472
-135
lines changed

Doc/whatsnew/3.12.rst

+13
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,19 @@ Pending Removal in Python 3.14
844844
use :func:`importlib.util.find_spec` instead.
845845
(Contributed by Nikita Sobolev in :gh:`97850`.)
846846

847+
* The following :mod:`ast` features have been deprecated in documentation since
848+
Python 3.8, now cause a :exc:`DeprecationWarning` to be emitted at runtime
849+
when they are accessed or used, and will be removed in Python 3.14:
850+
851+
* :class:`!ast.Num`
852+
* :class:`!ast.Str`
853+
* :class:`!ast.Bytes`
854+
* :class:`!ast.NameConstant`
855+
* :class:`!ast.Ellipsis`
856+
857+
Use :class:`ast.Constant` instead.
858+
(Contributed by Serhiy Storchaka in :gh:`90953`.)
859+
847860
Pending Removal in Future Versions
848861
----------------------------------
849862

Lib/ast.py

+74-8
Original file line numberDiff line numberDiff line change
@@ -294,9 +294,7 @@ def get_docstring(node, clean=True):
294294
if not(node.body and isinstance(node.body[0], Expr)):
295295
return None
296296
node = node.body[0].value
297-
if isinstance(node, Str):
298-
text = node.s
299-
elif isinstance(node, Constant) and isinstance(node.value, str):
297+
if isinstance(node, Constant) and isinstance(node.value, str):
300298
text = node.value
301299
else:
302300
return None
@@ -499,27 +497,66 @@ def generic_visit(self, node):
499497
return node
500498

501499

500+
_DEPRECATED_VALUE_ALIAS_MESSAGE = (
501+
"{name} is deprecated and will be removed in Python {remove}; use value instead"
502+
)
503+
_DEPRECATED_CLASS_MESSAGE = (
504+
"{name} is deprecated and will be removed in Python {remove}; "
505+
"use ast.Constant instead"
506+
)
507+
508+
502509
# If the ast module is loaded more than once, only add deprecated methods once
503510
if not hasattr(Constant, 'n'):
504511
# The following code is for backward compatibility.
505512
# It will be removed in future.
506513

507-
def _getter(self):
514+
def _n_getter(self):
515+
"""Deprecated. Use value instead."""
516+
import warnings
517+
warnings._deprecated(
518+
"Attribute n", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
519+
)
520+
return self.value
521+
522+
def _n_setter(self, value):
523+
import warnings
524+
warnings._deprecated(
525+
"Attribute n", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
526+
)
527+
self.value = value
528+
529+
def _s_getter(self):
508530
"""Deprecated. Use value instead."""
531+
import warnings
532+
warnings._deprecated(
533+
"Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
534+
)
509535
return self.value
510536

511-
def _setter(self, value):
537+
def _s_setter(self, value):
538+
import warnings
539+
warnings._deprecated(
540+
"Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
541+
)
512542
self.value = value
513543

514-
Constant.n = property(_getter, _setter)
515-
Constant.s = property(_getter, _setter)
544+
Constant.n = property(_n_getter, _n_setter)
545+
Constant.s = property(_s_getter, _s_setter)
516546

517547
class _ABC(type):
518548

519549
def __init__(cls, *args):
520550
cls.__doc__ = """Deprecated AST node class. Use ast.Constant instead"""
521551

522552
def __instancecheck__(cls, inst):
553+
if cls in _const_types:
554+
import warnings
555+
warnings._deprecated(
556+
f"ast.{cls.__qualname__}",
557+
message=_DEPRECATED_CLASS_MESSAGE,
558+
remove=(3, 14)
559+
)
523560
if not isinstance(inst, Constant):
524561
return False
525562
if cls in _const_types:
@@ -543,6 +580,10 @@ def _new(cls, *args, **kwargs):
543580
if pos < len(args):
544581
raise TypeError(f"{cls.__name__} got multiple values for argument {key!r}")
545582
if cls in _const_types:
583+
import warnings
584+
warnings._deprecated(
585+
f"ast.{cls.__qualname__}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
586+
)
546587
return Constant(*args, **kwargs)
547588
return Constant.__new__(cls, *args, **kwargs)
548589

@@ -565,10 +606,19 @@ class Ellipsis(Constant, metaclass=_ABC):
565606
_fields = ()
566607

567608
def __new__(cls, *args, **kwargs):
568-
if cls is Ellipsis:
609+
if cls is _ast_Ellipsis:
610+
import warnings
611+
warnings._deprecated(
612+
"ast.Ellipsis", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
613+
)
569614
return Constant(..., *args, **kwargs)
570615
return Constant.__new__(cls, *args, **kwargs)
571616

617+
# Keep another reference to Ellipsis in the global namespace
618+
# so it can be referenced in Ellipsis.__new__
619+
# (The original "Ellipsis" name is removed from the global namespace later on)
620+
_ast_Ellipsis = Ellipsis
621+
572622
_const_types = {
573623
Num: (int, float, complex),
574624
Str: (str,),
@@ -1699,6 +1749,22 @@ def unparse(ast_obj):
16991749
return unparser.visit(ast_obj)
17001750

17011751

1752+
_deprecated_globals = {
1753+
name: globals().pop(name)
1754+
for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis')
1755+
}
1756+
1757+
def __getattr__(name):
1758+
if name in _deprecated_globals:
1759+
globals()[name] = value = _deprecated_globals[name]
1760+
import warnings
1761+
warnings._deprecated(
1762+
f"ast.{name}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
1763+
)
1764+
return value
1765+
raise AttributeError(f"module 'ast' has no attribute '{name}'")
1766+
1767+
17021768
def main():
17031769
import argparse
17041770

0 commit comments

Comments
 (0)