Thanks to visit codestin.com
Credit goes to github.com

Skip to content

gh-119562: Remove AST nodes deprecated since Python 3.8 #119563

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,36 @@ Deprecated
Removed
=======

ast
---

* Remove the following classes. They were all deprecated since Python 3.8,
and have emitted deprecation warnings since Python 3.12:

* :class:`!ast.Num`
* :class:`!ast.Str`
* :class:`!ast.Bytes`
* :class:`!ast.NameConstant`
* :class:`!ast.Ellipsis`

Use :class:`ast.Constant` instead. As a consequence of these removals,
user-defined ``visit_Num``, ``visit_Str``, ``visit_Bytes``,
``visit_NameConstant`` and ``visit_Ellipsis`` methods on custom
:class:`ast.NodeVisitor` subclasses will no longer be called when the
``NodeVisitor`` subclass is visiting an AST. Define a ``visit_Constant``
method instead.

Also, remove the following deprecated properties on :class:`ast.Constant`,
which were present for compatibility with the now-removed AST classes:

* :attr:`!ast.Constant.n`
* :attr:`!ast.Constant.s`

Use :attr:`!ast.Constant.value` instead.

(Contributed by Alex Waygood in :gh:`119562`.)


argparse
--------

Expand Down
174 changes: 1 addition & 173 deletions Lib/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,27 +508,6 @@ def generic_visit(self, node):
elif isinstance(value, AST):
self.visit(value)

def visit_Constant(self, node):
Copy link
Contributor

@AraHaan AraHaan May 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh I think you accidentally removed this. Above said to use this. πŸ˜„

Copy link
Member Author

@AlexWaygood AlexWaygood May 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I deliberately removed it. It will still work if you define visit_Constant on a custom subclass of ast.NodeVisitor. The reason why it is currently defined on ast.NodeVisitor itself β€” uniquely among the visit_* methods β€” is to ensure that the deprecated methods visit_Num, visit_Str, etc. continue to work if you define them on a subclass of ast.NodeVisitor.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to clarify this a bit in 40b5965 -- hopefully that helps!

value = node.value
type_name = _const_node_type_names.get(type(value))
if type_name is None:
for cls, name in _const_node_type_names.items():
if isinstance(value, cls):
type_name = name
break
if type_name is not None:
method = 'visit_' + type_name
try:
visitor = getattr(self, method)
except AttributeError:
pass
else:
import warnings
warnings.warn(f"{method} is deprecated; add visit_Constant",
DeprecationWarning, 2)
return visitor(node)
return self.generic_visit(node)


class NodeTransformer(NodeVisitor):
"""
Expand Down Expand Up @@ -597,142 +576,6 @@ def generic_visit(self, node):
"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.
# It will be removed in future.

def _n_getter(self):
"""Deprecated. Use value instead."""
import warnings
warnings._deprecated(
"Attribute n", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
)
return self.value

def _n_setter(self, value):
import warnings
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._deprecated(
"Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
)
return self.value

def _s_setter(self, value):
import warnings
warnings._deprecated(
"Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
)
self.value = value

Constant.n = property(_n_getter, _n_setter)
Constant.s = property(_s_getter, _s_setter)

class _ABC(type):

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._deprecated(
f"ast.{cls.__qualname__}",
message=_DEPRECATED_CLASS_MESSAGE,
remove=(3, 14)
)
if not isinstance(inst, Constant):
return False
if cls in _const_types:
try:
value = inst.value
except AttributeError:
return False
else:
return (
isinstance(value, _const_types[cls]) and
not isinstance(value, _const_types_not.get(cls, ()))
)
return type.__instancecheck__(cls, inst)

def _new(cls, *args, **kwargs):
for key in kwargs:
if key not in cls._fields:
# arbitrary keyword arguments are accepted
continue
pos = cls._fields.index(key)
if pos < len(args):
raise TypeError(f"{cls.__name__} got multiple values for argument {key!r}")
if cls in _const_types:
import warnings
warnings._deprecated(
f"ast.{cls.__qualname__}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
)
return Constant(*args, **kwargs)
return Constant.__new__(cls, *args, **kwargs)

class Num(Constant, metaclass=_ABC):
_fields = ('n',)
__new__ = _new

class Str(Constant, metaclass=_ABC):
_fields = ('s',)
__new__ = _new

class Bytes(Constant, metaclass=_ABC):
_fields = ('s',)
__new__ = _new

class NameConstant(Constant, metaclass=_ABC):
__new__ = _new

class Ellipsis(Constant, metaclass=_ABC):
_fields = ()

def __new__(cls, *args, **kwargs):
if cls is _ast_Ellipsis:
import warnings
warnings._deprecated(
"ast.Ellipsis", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
)
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 = {
Num: (int, float, complex),
Str: (str,),
Bytes: (bytes,),
NameConstant: (type(None), bool),
Ellipsis: (type(...),),
}
_const_types_not = {
Num: (bool,),
}

_const_node_type_names = {
bool: 'NameConstant', # should be before int
type(None): 'NameConstant',
int: 'Num',
float: 'Num',
complex: 'Num',
str: 'Str',
bytes: 'Bytes',
type(...): 'Ellipsis',
}

class slice(AST):
"""Deprecated AST node class."""

Expand Down Expand Up @@ -1884,27 +1727,12 @@ def visit_MatchOr(self, node):
self.set_precedence(_Precedence.BOR.next(), *node.patterns)
self.interleave(lambda: self.write(" | "), self.traverse, node.patterns)


def unparse(ast_obj):
unparser = _Unparser()
return unparser.visit(ast_obj)


_deprecated_globals = {
name: globals().pop(name)
for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis')
}

def __getattr__(name):
if name in _deprecated_globals:
globals()[name] = value = _deprecated_globals[name]
import warnings
warnings._deprecated(
f"ast.{name}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
)
return value
raise AttributeError(f"module 'ast' has no attribute '{name}'")


def main():
import argparse

Expand Down
Loading
Loading