From e5f0f45865768f63deff72f157984416b840afcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 15 Jul 2024 19:08:46 +0200 Subject: [PATCH 01/10] fix symtable.Class.get_methods() (again) --- Lib/symtable.py | 5 +++++ Lib/test/test_symtable.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/Lib/symtable.py b/Lib/symtable.py index 221aaf88d41670..62cdf30a17429e 100644 --- a/Lib/symtable.py +++ b/Lib/symtable.py @@ -249,6 +249,11 @@ def is_local_symbol(ident): if is_local_symbol(st.name): match st.type: case _symtable.TYPE_FUNCTION: + # generators are of type TYPE_FUNCTION with a ".0" + # parameter as a first parameter (which makes them + # distinguishable from a function named 'genexpr') + if st.name == 'genexpr' and '.0' in st.varnames: + continue d[st.name] = 1 case _symtable.TYPE_TYPE_PARAMETERS: # Get the function-def block in the annotation diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py index 0cc192655931ba..f1552c559ea3ad 100644 --- a/Lib/test/test_symtable.py +++ b/Lib/test/test_symtable.py @@ -377,6 +377,36 @@ def test_class_info(self): 'glob_assigned_async_meth', 'glob_assigned_async_meth_pep_695', )) + def test_genexpr_class_info(self): + # Test generator expressions that are of type TYPE_FUNCTION + # but will not be reported by get_methods() since they are + # not functions per se. + # + # Other kind of comprehensions such as list, set or dict + # expressions do not have the TYPE_FUNCTION type. + + for paramlist in ('()', '(self)', '(a, b, c)'): + top = symtable.symtable("class A:\n" + f" def genexpr{paramlist}:\n" + " pass\n", + "?", "exec") + this = find_block(top, "A") + self.assertEqual(this.get_methods(), ('genexpr',)) + + top = symtable.symtable("class A:\n" + " x = (x for x in [])\n" + f" def genexpr{paramlist}:\n" + " pass\n", + "?", "exec") + this = find_block(top, "A") + self.assertEqual(this.get_methods(), ('genexpr',)) + + top = symtable.symtable("class A:\n" + " genexpr = (x for x in [])\n", + "?", "exec") + this = find_block(top, "A") + self.assertEqual(this.get_methods(), ()) + def test_filename_correct(self): ### Bug tickler: SyntaxError file name correct whether error raised ### while parsing or building symbol table. From 9adbb27140693f3093ee8f76fb672afe6027ebae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:34:38 +0200 Subject: [PATCH 02/10] address review --- Lib/symtable.py | 15 +++++- Lib/test/test_symtable.py | 110 +++++++++++++++++++++++++------------- 2 files changed, 86 insertions(+), 39 deletions(-) diff --git a/Lib/symtable.py b/Lib/symtable.py index 62cdf30a17429e..7a30e1ac4ca378 100644 --- a/Lib/symtable.py +++ b/Lib/symtable.py @@ -237,6 +237,12 @@ class Class(SymbolTable): def get_methods(self): """Return a tuple of methods declared in the class. """ + import warnings + typename = f'{self.__class__.__module__}.{self.__class__.__name__}' + warnings.warn(f'{typename}.get_methods() is deprecated ' + f'and will be removed in Python 3.16.', + DeprecationWarning, stacklevel=2) + if self.__methods is None: d = {} @@ -261,7 +267,14 @@ def is_local_symbol(ident): scope_name = st.name for c in st.children: if c.name == scope_name and c.type == _symtable.TYPE_FUNCTION: - d[st.name] = 1 + # A generic generator of type TYPE_FUNCTION + # cannot be a direct child of 'st' (but it + # can be a descendant), e.g.: + # + # class A: + # type genexpr[genexpr] = (x for x in []) + assert scope_name != 'genexpr' or '.0' not in c.varnames + d[scope_name] = 1 break self.__methods = tuple(d) return self.__methods diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py index f1552c559ea3ad..e274560bbc414b 100644 --- a/Lib/test/test_symtable.py +++ b/Lib/test/test_symtable.py @@ -1,6 +1,10 @@ """ Test the API of the symtable module. """ +import itertools + +import re +import textwrap import symtable import unittest @@ -356,28 +360,34 @@ def test_name(self): self.assertEqual(self.spam.lookup("x").get_name(), "x") self.assertEqual(self.Mine.get_name(), "Mine") - def test_class_info(self): - self.assertEqual(self.Mine.get_methods(), ('a_method',)) + def test_class_get_methods(self): + deprecation_mess = ( + re.escape('symtable.Class.get_methods() is deprecated ' + 'and will be removed in Python 3.16.') + ) + + with self.assertWarnsRegex(DeprecationWarning, deprecation_mess): + self.assertEqual(self.Mine.get_methods(), ('a_method',)) top = symtable.symtable(TEST_COMPLEX_CLASS_CODE, "?", "exec") this = find_block(top, "ComplexClass") - self.assertEqual(this.get_methods(), ( - 'a_method', 'a_method_pep_695', - 'an_async_method', 'an_async_method_pep_695', - 'a_classmethod', 'a_classmethod_pep_695', - 'an_async_classmethod', 'an_async_classmethod_pep_695', - 'a_staticmethod', 'a_staticmethod_pep_695', - 'an_async_staticmethod', 'an_async_staticmethod_pep_695', - 'a_fakemethod', 'a_fakemethod_pep_695', - 'an_async_fakemethod', 'an_async_fakemethod_pep_695', - 'glob_unassigned_meth', 'glob_unassigned_meth_pep_695', - 'glob_unassigned_async_meth', 'glob_unassigned_async_meth_pep_695', - 'glob_assigned_meth', 'glob_assigned_meth_pep_695', - 'glob_assigned_async_meth', 'glob_assigned_async_meth_pep_695', - )) - - def test_genexpr_class_info(self): + with self.assertWarnsRegex(DeprecationWarning, deprecation_mess): + self.assertEqual(this.get_methods(), ( + 'a_method', 'a_method_pep_695', + 'an_async_method', 'an_async_method_pep_695', + 'a_classmethod', 'a_classmethod_pep_695', + 'an_async_classmethod', 'an_async_classmethod_pep_695', + 'a_staticmethod', 'a_staticmethod_pep_695', + 'an_async_staticmethod', 'an_async_staticmethod_pep_695', + 'a_fakemethod', 'a_fakemethod_pep_695', + 'an_async_fakemethod', 'an_async_fakemethod_pep_695', + 'glob_unassigned_meth', 'glob_unassigned_meth_pep_695', + 'glob_unassigned_async_meth', 'glob_unassigned_async_meth_pep_695', + 'glob_assigned_meth', 'glob_assigned_meth_pep_695', + 'glob_assigned_async_meth', 'glob_assigned_async_meth_pep_695', + )) + # Test generator expressions that are of type TYPE_FUNCTION # but will not be reported by get_methods() since they are # not functions per se. @@ -385,27 +395,51 @@ def test_genexpr_class_info(self): # Other kind of comprehensions such as list, set or dict # expressions do not have the TYPE_FUNCTION type. - for paramlist in ('()', '(self)', '(a, b, c)'): - top = symtable.symtable("class A:\n" - f" def genexpr{paramlist}:\n" - " pass\n", - "?", "exec") + def check_body(body, expected_methods): + indented = textwrap.indent(body, ' ' * 4) + top = symtable.symtable(f"class A:\n{indented}", "?", "exec") this = find_block(top, "A") - self.assertEqual(this.get_methods(), ('genexpr',)) - - top = symtable.symtable("class A:\n" - " x = (x for x in [])\n" - f" def genexpr{paramlist}:\n" - " pass\n", - "?", "exec") - this = find_block(top, "A") - self.assertEqual(this.get_methods(), ('genexpr',)) - - top = symtable.symtable("class A:\n" - " genexpr = (x for x in [])\n", - "?", "exec") - this = find_block(top, "A") - self.assertEqual(this.get_methods(), ()) + with self.assertWarnsRegex(DeprecationWarning, deprecation_mess): + self.assertEqual(this.get_methods(), expected_methods) + + # statements with 'genexpr' inside it + GENEXPRS = ( + 'x = (x for x in [])', + 'x = (x async for x in [])', + 'type x[genexpr = (x for x in [])] = (x for x in [])', + 'type x[genexpr = (x async for x in [])] = (x async for x in [])', + 'genexpr = (x for x in [])', + 'genexpr = (x async for x in [])', + 'type genexpr[genexpr = (x for x in [])] = (x for x in [])', + 'type genexpr[genexpr = (x async for x in [])] = (x async for x in [])', + ) + + for gen in GENEXPRS: + # test generator expression + with self.subTest(gen=gen): + check_body(gen, ()) + + # test generator expression + variable named 'genexpr' + with self.subTest(gen=gen, isvar=True): + check_body('\n'.join((gen, 'genexpr = 1')), ()) + check_body('\n'.join(('genexpr = 1', gen)), ()) + + for paramlist in ('()', '(x)', '(x, y)', '(z: T)'): + for func in ( + f'def genexpr{paramlist}:pass', + f'async def genexpr{paramlist}:pass', + f'def genexpr[T]{paramlist}:pass', + f'async def genexpr[T]{paramlist}:pass', + ): + with self.subTest(func=func): + # test function named 'genexpr' + check_body(func, ('genexpr',)) + + for gen in GENEXPRS: + with self.subTest(gen=gen, func=func): + # test generator expression + function named 'genexpr' + check_body('\n'.join((gen, func)), ('genexpr',)) + check_body('\n'.join((func, gen)), ('genexpr',)) def test_filename_correct(self): ### Bug tickler: SyntaxError file name correct whether error raised From e9d00c087b911f19e10bd781fe82c1adbb7345b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:51:21 +0200 Subject: [PATCH 03/10] update docs --- Doc/library/symtable.rst | 3 +++ .../Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Doc/library/symtable.rst b/Doc/library/symtable.rst index aa5f8d95925ada..fd2dd59cc87f99 100644 --- a/Doc/library/symtable.rst +++ b/Doc/library/symtable.rst @@ -214,6 +214,9 @@ Examining Symbol Tables Although ``A().f()`` raises :exc:`TypeError` at runtime, ``A.f`` is still considered as a method-like function. + .. deprecated-removed:: 3.14 3.16 + + .. class:: Symbol An entry in a :class:`SymbolTable` corresponding to an identifier in the diff --git a/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst b/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst index d4cca1439816b0..7dc16877f906f1 100644 --- a/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst +++ b/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst @@ -1,2 +1,3 @@ -Fix :meth:`symtable.Class.get_methods` and document its behaviour. Patch by -Bénédikt Tran. +Fix :meth:`symtable.Class.get_methods` and document its behaviour. Due to the +lack of interest, the method is now marked as deprecated and will be removed +in Python 3.16. Patch by Bénédikt Tran. From 0981fcf3598be10786c4dd11bc636735c1321ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:54:40 +0200 Subject: [PATCH 04/10] remove unused imports --- Lib/test/test_symtable.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py index e274560bbc414b..bd367c1591c744 100644 --- a/Lib/test/test_symtable.py +++ b/Lib/test/test_symtable.py @@ -1,7 +1,6 @@ """ Test the API of the symtable module. """ -import itertools import re import textwrap From 89eb3703cef1110415b1cba5968a1cac22a89c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 16 Jul 2024 11:23:44 +0200 Subject: [PATCH 05/10] fix doctest? --- Doc/library/symtable.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/symtable.rst b/Doc/library/symtable.rst index fd2dd59cc87f99..e6db8ae290b21e 100644 --- a/Doc/library/symtable.rst +++ b/Doc/library/symtable.rst @@ -208,8 +208,8 @@ Examining Symbol Tables ... def outer(self): pass ... ''', 'test', 'exec') >>> class_A = st.get_children()[2] - >>> class_A.get_methods() - ('f', 'g', 'h') + >>> class_A.get_methods() == ('f', 'g', 'h') + True Although ``A().f()`` raises :exc:`TypeError` at runtime, ``A.f`` is still considered as a method-like function. From 0781ccde47d97add8e8bb895bf4920aaafe20404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:44:29 +0200 Subject: [PATCH 06/10] add deprecation notice --- Doc/whatsnew/3.14.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 8f7b6ebd0af316..128aaf1081109d 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -167,6 +167,9 @@ Deprecated write new code. The :mod:`subprocess` module is recommended instead. (Contributed by Victor Stinner in :gh:`120743`.) +* Deprecate :meth:`symtable.Class.get_methods` due to the lack of interest. + (Contributed by Bénédikt Tran in :gh:`119698`.) + Removed ======= From e1402f2aaa221f2dac3564674d41c734476d595a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:44:35 +0200 Subject: [PATCH 07/10] configure doctest correctly? --- Doc/library/symtable.rst | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Doc/library/symtable.rst b/Doc/library/symtable.rst index e6db8ae290b21e..8ebcb3bcf1b7b4 100644 --- a/Doc/library/symtable.rst +++ b/Doc/library/symtable.rst @@ -191,6 +191,19 @@ Examining Symbol Tables For example: + .. testsetup:: symtable.Class.get_methods + + import warnings + context = warnings.catch_warnings() + context.__enter__() + warnings.simplefilter("ignore", category=DeprecationWarning) + + .. testcleanup:: symtable.Class.get_methods + + context.__exit__() + + .. doctest:: symtable.Class.get_methods + >>> import symtable >>> st = symtable.symtable(''' ... def outer(): pass @@ -208,8 +221,8 @@ Examining Symbol Tables ... def outer(self): pass ... ''', 'test', 'exec') >>> class_A = st.get_children()[2] - >>> class_A.get_methods() == ('f', 'g', 'h') - True + >>> class_A.get_methods() + ('f', 'g', 'h') Although ``A().f()`` raises :exc:`TypeError` at runtime, ``A.f`` is still considered as a method-like function. From 8a06e53591125d95d5bd9e575b83eecde55031ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:40:17 +0200 Subject: [PATCH 08/10] remove deprecation notice for a separate PR --- Doc/library/symtable.rst | 13 ------ Doc/whatsnew/3.14.rst | 3 -- Lib/symtable.py | 6 --- Lib/test/test_symtable.py | 41 ++++++++----------- ...-06-06-12-07-57.gh-issue-119698.rRrprk.rst | 5 +-- 5 files changed, 18 insertions(+), 50 deletions(-) diff --git a/Doc/library/symtable.rst b/Doc/library/symtable.rst index 8ebcb3bcf1b7b4..8afdc8e97b4712 100644 --- a/Doc/library/symtable.rst +++ b/Doc/library/symtable.rst @@ -191,17 +191,6 @@ Examining Symbol Tables For example: - .. testsetup:: symtable.Class.get_methods - - import warnings - context = warnings.catch_warnings() - context.__enter__() - warnings.simplefilter("ignore", category=DeprecationWarning) - - .. testcleanup:: symtable.Class.get_methods - - context.__exit__() - .. doctest:: symtable.Class.get_methods >>> import symtable @@ -227,8 +216,6 @@ Examining Symbol Tables Although ``A().f()`` raises :exc:`TypeError` at runtime, ``A.f`` is still considered as a method-like function. - .. deprecated-removed:: 3.14 3.16 - .. class:: Symbol diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 128aaf1081109d..8f7b6ebd0af316 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -167,9 +167,6 @@ Deprecated write new code. The :mod:`subprocess` module is recommended instead. (Contributed by Victor Stinner in :gh:`120743`.) -* Deprecate :meth:`symtable.Class.get_methods` due to the lack of interest. - (Contributed by Bénédikt Tran in :gh:`119698`.) - Removed ======= diff --git a/Lib/symtable.py b/Lib/symtable.py index 7a30e1ac4ca378..8c796b7cc7edb8 100644 --- a/Lib/symtable.py +++ b/Lib/symtable.py @@ -237,12 +237,6 @@ class Class(SymbolTable): def get_methods(self): """Return a tuple of methods declared in the class. """ - import warnings - typename = f'{self.__class__.__module__}.{self.__class__.__name__}' - warnings.warn(f'{typename}.get_methods() is deprecated ' - f'and will be removed in Python 3.16.', - DeprecationWarning, stacklevel=2) - if self.__methods is None: d = {} diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py index bd367c1591c744..82f667d3687fee 100644 --- a/Lib/test/test_symtable.py +++ b/Lib/test/test_symtable.py @@ -2,7 +2,6 @@ Test the API of the symtable module. """ -import re import textwrap import symtable import unittest @@ -360,32 +359,25 @@ def test_name(self): self.assertEqual(self.Mine.get_name(), "Mine") def test_class_get_methods(self): - deprecation_mess = ( - re.escape('symtable.Class.get_methods() is deprecated ' - 'and will be removed in Python 3.16.') - ) - - with self.assertWarnsRegex(DeprecationWarning, deprecation_mess): - self.assertEqual(self.Mine.get_methods(), ('a_method',)) + self.assertEqual(self.Mine.get_methods(), ('a_method',)) top = symtable.symtable(TEST_COMPLEX_CLASS_CODE, "?", "exec") this = find_block(top, "ComplexClass") - with self.assertWarnsRegex(DeprecationWarning, deprecation_mess): - self.assertEqual(this.get_methods(), ( - 'a_method', 'a_method_pep_695', - 'an_async_method', 'an_async_method_pep_695', - 'a_classmethod', 'a_classmethod_pep_695', - 'an_async_classmethod', 'an_async_classmethod_pep_695', - 'a_staticmethod', 'a_staticmethod_pep_695', - 'an_async_staticmethod', 'an_async_staticmethod_pep_695', - 'a_fakemethod', 'a_fakemethod_pep_695', - 'an_async_fakemethod', 'an_async_fakemethod_pep_695', - 'glob_unassigned_meth', 'glob_unassigned_meth_pep_695', - 'glob_unassigned_async_meth', 'glob_unassigned_async_meth_pep_695', - 'glob_assigned_meth', 'glob_assigned_meth_pep_695', - 'glob_assigned_async_meth', 'glob_assigned_async_meth_pep_695', - )) + self.assertEqual(this.get_methods(), ( + 'a_method', 'a_method_pep_695', + 'an_async_method', 'an_async_method_pep_695', + 'a_classmethod', 'a_classmethod_pep_695', + 'an_async_classmethod', 'an_async_classmethod_pep_695', + 'a_staticmethod', 'a_staticmethod_pep_695', + 'an_async_staticmethod', 'an_async_staticmethod_pep_695', + 'a_fakemethod', 'a_fakemethod_pep_695', + 'an_async_fakemethod', 'an_async_fakemethod_pep_695', + 'glob_unassigned_meth', 'glob_unassigned_meth_pep_695', + 'glob_unassigned_async_meth', 'glob_unassigned_async_meth_pep_695', + 'glob_assigned_meth', 'glob_assigned_meth_pep_695', + 'glob_assigned_async_meth', 'glob_assigned_async_meth_pep_695', + )) # Test generator expressions that are of type TYPE_FUNCTION # but will not be reported by get_methods() since they are @@ -398,8 +390,7 @@ def check_body(body, expected_methods): indented = textwrap.indent(body, ' ' * 4) top = symtable.symtable(f"class A:\n{indented}", "?", "exec") this = find_block(top, "A") - with self.assertWarnsRegex(DeprecationWarning, deprecation_mess): - self.assertEqual(this.get_methods(), expected_methods) + self.assertEqual(this.get_methods(), expected_methods) # statements with 'genexpr' inside it GENEXPRS = ( diff --git a/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst b/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst index 7dc16877f906f1..fc53e3e4d31de2 100644 --- a/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst +++ b/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst @@ -1,3 +1,2 @@ -Fix :meth:`symtable.Class.get_methods` and document its behaviour. Due to the -lack of interest, the method is now marked as deprecated and will be removed -in Python 3.16. Patch by Bénédikt Tran. +Fix :meth:`symtable.Class.get_methods` and document its behaviour. +Patch by Bénédikt Tran. From 8006e3179d6bcb61cb16a95845487c2e86abde98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:41:40 +0200 Subject: [PATCH 09/10] deprecate symtable.Class.get_methods --- Doc/library/symtable.rst | 13 ++++++ Doc/whatsnew/3.14.rst | 3 ++ Lib/symtable.py | 6 +++ Lib/test/test_symtable.py | 41 +++++++++++-------- ...-06-06-12-07-57.gh-issue-119698.rRrprk.rst | 5 ++- 5 files changed, 50 insertions(+), 18 deletions(-) diff --git a/Doc/library/symtable.rst b/Doc/library/symtable.rst index 8afdc8e97b4712..8ebcb3bcf1b7b4 100644 --- a/Doc/library/symtable.rst +++ b/Doc/library/symtable.rst @@ -191,6 +191,17 @@ Examining Symbol Tables For example: + .. testsetup:: symtable.Class.get_methods + + import warnings + context = warnings.catch_warnings() + context.__enter__() + warnings.simplefilter("ignore", category=DeprecationWarning) + + .. testcleanup:: symtable.Class.get_methods + + context.__exit__() + .. doctest:: symtable.Class.get_methods >>> import symtable @@ -216,6 +227,8 @@ Examining Symbol Tables Although ``A().f()`` raises :exc:`TypeError` at runtime, ``A.f`` is still considered as a method-like function. + .. deprecated-removed:: 3.14 3.16 + .. class:: Symbol diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 8f7b6ebd0af316..128aaf1081109d 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -167,6 +167,9 @@ Deprecated write new code. The :mod:`subprocess` module is recommended instead. (Contributed by Victor Stinner in :gh:`120743`.) +* Deprecate :meth:`symtable.Class.get_methods` due to the lack of interest. + (Contributed by Bénédikt Tran in :gh:`119698`.) + Removed ======= diff --git a/Lib/symtable.py b/Lib/symtable.py index 8c796b7cc7edb8..7a30e1ac4ca378 100644 --- a/Lib/symtable.py +++ b/Lib/symtable.py @@ -237,6 +237,12 @@ class Class(SymbolTable): def get_methods(self): """Return a tuple of methods declared in the class. """ + import warnings + typename = f'{self.__class__.__module__}.{self.__class__.__name__}' + warnings.warn(f'{typename}.get_methods() is deprecated ' + f'and will be removed in Python 3.16.', + DeprecationWarning, stacklevel=2) + if self.__methods is None: d = {} diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py index 82f667d3687fee..bd367c1591c744 100644 --- a/Lib/test/test_symtable.py +++ b/Lib/test/test_symtable.py @@ -2,6 +2,7 @@ Test the API of the symtable module. """ +import re import textwrap import symtable import unittest @@ -359,25 +360,32 @@ def test_name(self): self.assertEqual(self.Mine.get_name(), "Mine") def test_class_get_methods(self): - self.assertEqual(self.Mine.get_methods(), ('a_method',)) + deprecation_mess = ( + re.escape('symtable.Class.get_methods() is deprecated ' + 'and will be removed in Python 3.16.') + ) + + with self.assertWarnsRegex(DeprecationWarning, deprecation_mess): + self.assertEqual(self.Mine.get_methods(), ('a_method',)) top = symtable.symtable(TEST_COMPLEX_CLASS_CODE, "?", "exec") this = find_block(top, "ComplexClass") - self.assertEqual(this.get_methods(), ( - 'a_method', 'a_method_pep_695', - 'an_async_method', 'an_async_method_pep_695', - 'a_classmethod', 'a_classmethod_pep_695', - 'an_async_classmethod', 'an_async_classmethod_pep_695', - 'a_staticmethod', 'a_staticmethod_pep_695', - 'an_async_staticmethod', 'an_async_staticmethod_pep_695', - 'a_fakemethod', 'a_fakemethod_pep_695', - 'an_async_fakemethod', 'an_async_fakemethod_pep_695', - 'glob_unassigned_meth', 'glob_unassigned_meth_pep_695', - 'glob_unassigned_async_meth', 'glob_unassigned_async_meth_pep_695', - 'glob_assigned_meth', 'glob_assigned_meth_pep_695', - 'glob_assigned_async_meth', 'glob_assigned_async_meth_pep_695', - )) + with self.assertWarnsRegex(DeprecationWarning, deprecation_mess): + self.assertEqual(this.get_methods(), ( + 'a_method', 'a_method_pep_695', + 'an_async_method', 'an_async_method_pep_695', + 'a_classmethod', 'a_classmethod_pep_695', + 'an_async_classmethod', 'an_async_classmethod_pep_695', + 'a_staticmethod', 'a_staticmethod_pep_695', + 'an_async_staticmethod', 'an_async_staticmethod_pep_695', + 'a_fakemethod', 'a_fakemethod_pep_695', + 'an_async_fakemethod', 'an_async_fakemethod_pep_695', + 'glob_unassigned_meth', 'glob_unassigned_meth_pep_695', + 'glob_unassigned_async_meth', 'glob_unassigned_async_meth_pep_695', + 'glob_assigned_meth', 'glob_assigned_meth_pep_695', + 'glob_assigned_async_meth', 'glob_assigned_async_meth_pep_695', + )) # Test generator expressions that are of type TYPE_FUNCTION # but will not be reported by get_methods() since they are @@ -390,7 +398,8 @@ def check_body(body, expected_methods): indented = textwrap.indent(body, ' ' * 4) top = symtable.symtable(f"class A:\n{indented}", "?", "exec") this = find_block(top, "A") - self.assertEqual(this.get_methods(), expected_methods) + with self.assertWarnsRegex(DeprecationWarning, deprecation_mess): + self.assertEqual(this.get_methods(), expected_methods) # statements with 'genexpr' inside it GENEXPRS = ( diff --git a/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst b/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst index fc53e3e4d31de2..7dc16877f906f1 100644 --- a/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst +++ b/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst @@ -1,2 +1,3 @@ -Fix :meth:`symtable.Class.get_methods` and document its behaviour. -Patch by Bénédikt Tran. +Fix :meth:`symtable.Class.get_methods` and document its behaviour. Due to the +lack of interest, the method is now marked as deprecated and will be removed +in Python 3.16. Patch by Bénédikt Tran. From 667fd0f5e0d3a395f51c0dee59ded19f0bc692c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:45:12 +0200 Subject: [PATCH 10/10] blurb --- .../Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst | 5 ++--- .../Library/2024-07-17-09-44-35.gh-issue-119698.WlygzR.rst | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-07-17-09-44-35.gh-issue-119698.WlygzR.rst diff --git a/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst b/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst index 7dc16877f906f1..d4cca1439816b0 100644 --- a/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst +++ b/Misc/NEWS.d/next/Library/2024-06-06-12-07-57.gh-issue-119698.rRrprk.rst @@ -1,3 +1,2 @@ -Fix :meth:`symtable.Class.get_methods` and document its behaviour. Due to the -lack of interest, the method is now marked as deprecated and will be removed -in Python 3.16. Patch by Bénédikt Tran. +Fix :meth:`symtable.Class.get_methods` and document its behaviour. Patch by +Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Library/2024-07-17-09-44-35.gh-issue-119698.WlygzR.rst b/Misc/NEWS.d/next/Library/2024-07-17-09-44-35.gh-issue-119698.WlygzR.rst new file mode 100644 index 00000000000000..5134e609e7f1ca --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-17-09-44-35.gh-issue-119698.WlygzR.rst @@ -0,0 +1,3 @@ +Due to the lack of interest for :meth:`symtable.Class.get_methods`, the +method is marked as deprecated and will be removed in Python 3.16. Patch by +Bénédikt Tran.