https://github.com/python/cpython/commit/2f2c2ced6b2aff74b41115d45e1713266f3e94fe
commit: 2f2c2ced6b2aff74b41115d45e1713266f3e94fe
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-19T11:38:52+03:00
summary:
gh-155263: Add the --list option to Argument Clinic (GH-155264)
It prints the modules, classes and functions which Argument Clinic
defines in the specified files, each function with its signature.
files:
A Misc/NEWS.d/next/Tools-Demos/2026-08-06-08-32-53.gh-issue-155263.RCcjtk.rst
M Lib/test/test_clinic.py
M Tools/clinic/libclinic/cli.py
M Tools/clinic/libclinic/dsl_parser.py
M Tools/clinic/libclinic/function.py
diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py
index 3bf654371b7364..d3e4454348d023 100644
--- a/Lib/test/test_clinic.py
+++ b/Lib/test/test_clinic.py
@@ -3681,6 +3681,163 @@ def test_cli_converters_no_converters(self):
f.write("/*[clinic input]\n[clinic start generated code]*/\n")
self.assertEqual(self.expect_success("--converters", fn), "")
+ LIST_CODE = dedent("""
+ /*[clinic input]
+ func
+ a: int
+ /
+
+ Docstring.
+ [clinic start generated code]*/
+
+ /*[clinic input]
+ cloned = func
+ [clinic start generated code]*/
+
+ /*[clinic input]
+ module m
+ class m.C "void *" ""
+ class m.C.D "void *" ""
+ [clinic start generated code]*/
+
+ /*[clinic input]
+ m.C.meth
+ self: self(type="void *")
+ a: object
+ [
+ b: object
+ ]
+ /
+
+ Docstring.
+ [clinic start generated code]*/
+
+ /*[clinic input]
+ @classmethod
+ m.C.__new__
+ a: object
+
+ Docstring.
+ [clinic start generated code]*/
+
+ /*[clinic input]
+ @getter
+ m.C.prop
+ [clinic start generated code]*/
+
+ /*[clinic input]
+ @setter
+ m.C.prop
+ [clinic start generated code]*/
+
+ /*[clinic input]
+ m.C.D.meth
+ self: self(type="void *")
+
+ Docstring.
+ [clinic start generated code]*/
+ """)
+
+ def make_list_file(self, tmp_dir):
+ fn = os.path.join(tmp_dir, "test.c")
+ with open(fn, "w", encoding="utf-8") as f:
+ f.write(self.LIST_CODE)
+ return fn
+
+ LIST_OUTPUT = [
+ " func($module, a, /)",
+ " cloned($module, a, /)",
+ " module m",
+ " class m.C",
+ # A signature with an option group is only for the docstring.
+ " m.C.meth(a, [b])",
+ " m.C(a)",
+ " getter m.C.prop",
+ " setter m.C.prop",
+ " class m.C.D",
+ " m.C.D.meth($self, /)",
+ ]
+
+ def test_cli_list(self):
+ with os_helper.temp_dir() as tmp_dir:
+ fn = self.make_list_file(tmp_dir)
+ pre_mtime = os.stat(fn).st_mtime_ns
+ out = self.expect_success("--list", fn)
+ self.assertEqual(out.splitlines(), [fn] + self.LIST_OUTPUT)
+ # Nothing is written.
+ with open(fn, encoding="utf-8") as f:
+ self.assertEqual(f.read(), self.LIST_CODE)
+ self.assertEqual(os.stat(fn).st_mtime_ns, pre_mtime)
+ self.assertEqual(os.listdir(tmp_dir), ["test.c"])
+
+ def test_cli_list_no_clinic_block(self):
+ with os_helper.temp_dir() as tmp_dir:
+ fn = os.path.join(tmp_dir, "test.c")
+ with open(fn, "w", encoding="utf-8") as f:
+ f.write("int x;\n")
+ self.assertEqual(self.expect_success("--list", fn), "")
+
+ def test_cli_list_no_definitions(self):
+ with os_helper.temp_dir() as tmp_dir:
+ fn = os.path.join(tmp_dir, "test.c")
+ with open(fn, "w", encoding="utf-8") as f:
+ f.write("/*[clinic input]\n[clinic start generated code]*/\n")
+ self.assertEqual(self.expect_success("--list", fn), "")
+
+ def test_cli_list_make(self):
+ with os_helper.temp_dir() as tmp_dir:
+ fn = self.make_list_file(tmp_dir)
+ out = self.expect_success("--list", "--make", "--srcdir", tmp_dir)
+ self.assertEqual(out.splitlines(), [fn] + self.LIST_OUTPUT)
+ self.assertEqual(os.listdir(tmp_dir), ["test.c"])
+
+ def test_cli_list_verbose(self):
+ with os_helper.temp_dir() as tmp_dir:
+ fn = self.make_list_file(tmp_dir)
+ # The progress does not mix with the report.
+ out, err, code = self.run_clinic("-v", "--list", fn)
+ self.assertEqual(code, 0)
+ self.assertEqual(err.splitlines(), [fn])
+ self.assertEqual(out.splitlines(), [fn] + self.LIST_OUTPUT)
+
+ def test_cli_list_checksum_mismatch(self):
+ with os_helper.temp_dir() as tmp_dir:
+ fn = self.make_list_file(tmp_dir)
+ with open(fn, "a", encoding="utf-8") as f:
+ f.write("/*[clinic end generated code: "
+ "output=0123456789abcdef input=fedcba9876543210]*/\n")
+ _, err = self.expect_failure("--list", fn)
+ self.assertIn("Checksum mismatch!", err)
+ # The check is skipped with --force.
+ out = self.expect_success("-f", "--list", fn)
+ self.assertEqual(out.splitlines(), [fn] + self.LIST_OUTPUT)
+ self.assertEqual(os.listdir(tmp_dir), ["test.c"])
+
+ def test_cli_list_external(self):
+ # A file which uses getters, setters and nested classes.
+ source = support.findfile('clinic.test.c')
+ out = self.expect_success("--list", source)
+ lines = out.splitlines()
+ self.assertEqual(lines[0], source)
+ for line in (" class Test",
+ " getter Test.property",
+ " setter Test.property",
+ " Test.class_method($type, /)",
+ " module m",
+ " class m.T"):
+ with self.subTest(line=line):
+ self.assertIn(line, lines)
+
+ def test_cli_fail_list_and_dry_run(self):
+ for opt in "--dry-run", "--diff":
+ with self.subTest(opt=opt):
+ _, err = self.expect_failure("--list", opt, "test.c")
+ self.assertIn("can't use --dry-run or --diff with --list", err)
+
+ def test_cli_fail_list_and_converters(self):
+ _, err = self.expect_failure("--list", "--converters", "test.c")
+ self.assertIn("can't use --converters with --list", err)
+
def test_cli_fail_directory(self):
with os_helper.temp_dir() as tmp_dir:
subdir = os.path.join(tmp_dir, "test.c")
diff --git
a/Misc/NEWS.d/next/Tools-Demos/2026-08-06-08-32-53.gh-issue-155263.RCcjtk.rst
b/Misc/NEWS.d/next/Tools-Demos/2026-08-06-08-32-53.gh-issue-155263.RCcjtk.rst
new file mode 100644
index 00000000000000..969efa1af808f5
--- /dev/null
+++
b/Misc/NEWS.d/next/Tools-Demos/2026-08-06-08-32-53.gh-issue-155263.RCcjtk.rst
@@ -0,0 +1,3 @@
+Add the ``--list`` option to Argument Clinic.
+It prints the modules, classes and functions which Argument Clinic defines in
+the specified files, each function with its signature.
diff --git a/Tools/clinic/libclinic/cli.py b/Tools/clinic/libclinic/cli.py
index 290fc3a6e59408..9629c173177945 100644
--- a/Tools/clinic/libclinic/cli.py
+++ b/Tools/clinic/libclinic/cli.py
@@ -22,6 +22,9 @@
return_converters, ReturnConverterType)
from libclinic.clanguage import CLanguage
from libclinic.app import Clinic
+from libclinic.dsl_parser import render_text_signature
+from libclinic.function import (
+ Class, Definition, Module, GETTER, SETTER, walk_definitions)
# TODO:
@@ -54,7 +57,7 @@ def parse_file(
output: str | None = None,
verify: bool = True,
writer: libclinic.FileWriter | None = None,
-) -> None:
+) -> Clinic | None:
if not output:
output = filename
if writer is None:
@@ -78,7 +81,7 @@ def parse_file(
# exit quickly if there are no clinic markers in the file
find_start_re = BlockParser("", language).find_start_re
if not find_start_re.search(raw):
- return
+ return None
if LIMITED_CAPI_REGEX.search(raw):
limited_capi = True
@@ -97,6 +100,31 @@ def parse_file(
writer.update_times(output,
[fn for fn, _ in files if fn != output],
any(changed for _, changed in files))
+ return clinic
+
+
+def format_definition(depth: int, name: str, definition: Definition) -> str:
+ indent = " " * (depth + 1)
+ if isinstance(definition, Module):
+ return f"{indent}module {name}"
+ if isinstance(definition, Class):
+ return f"{indent}class {name}"
+ if definition.kind is GETTER:
+ return f"{indent}getter {name}"
+ if definition.kind is SETTER:
+ return f"{indent}setter {name}"
+ signature = render_text_signature(definition, definition.render_parameters,
+ name=name, line_width=None)
+ return indent + signature
+
+
+def print_definitions(clinic: Clinic) -> None:
+ """Print the modules, classes and functions defined in the parsed file."""
+ lines = [format_definition(depth, name, definition)
+ for depth, name, definition in walk_definitions(clinic)]
+ if lines:
+ print(clinic.filename)
+ print("\n".join(lines))
def create_cli() -> argparse.ArgumentParser:
@@ -126,6 +154,10 @@ def create_cli() -> argparse.ArgumentParser:
"and return converters; if files are "
"specified, print only the converters "
"which they define"))
+ cmdline.add_argument("--list", action='store_true',
+ help=("don't write any file, only list the modules, "
+ "classes and functions which the specified "
+ "files define, with their signatures"))
cmdline.add_argument("--make", action='store_true',
help="walk --srcdir to run over all relevant files")
cmdline.add_argument("--srcdir", type=str, default=os.curdir,
@@ -252,7 +284,7 @@ def run_clinic(parser: argparse.ArgumentParser, ns:
argparse.Namespace) -> None:
dry_run = ns.dry_run or ns.diff
# The report is written to the standard output, so the progress
# is written to the standard error stream to not mix them.
- verbose_file = sys.stderr if dry_run else sys.stdout
+ verbose_file = sys.stderr if dry_run or ns.list else sys.stdout
filenames: Iterable[str]
if ns.make:
@@ -268,6 +300,12 @@ def run_clinic(parser: argparse.ArgumentParser, ns:
argparse.Namespace) -> None:
parser.error("can't use -o with multiple filenames")
filenames = ns.filename
+ if ns.list:
+ if dry_run:
+ parser.error("can't use --dry-run or --diff with --list")
+ if ns.converters:
+ parser.error("can't use --converters with --list")
+
if ns.converters:
if dry_run:
parser.error("can't use --dry-run or --diff with --converters")
@@ -280,20 +318,22 @@ def run_clinic(parser: argparse.ArgumentParser, ns:
argparse.Namespace) -> None:
builtin_legacy_converters = dict(legacy_converters)
builtin_return_converters = dict(return_converters)
- writer = libclinic.FileWriter(dry_run=dry_run or ns.converters)
+ writer = libclinic.FileWriter(dry_run=dry_run or ns.converters or ns.list)
for filename in filenames:
if ns.verbose:
print(filename, file=verbose_file)
- parse_file(filename, output=ns.output,
- verify=not ns.force, limited_capi=ns.limited_capi,
- writer=writer)
+ clinic = parse_file(filename, output=ns.output,
+ verify=not ns.force, limited_capi=ns.limited_capi,
+ writer=writer)
+ if ns.list and clinic is not None:
+ print_definitions(clinic)
if ns.converters:
print_converters(
defined_in_files(converters, builtin_converters),
defined_in_files(legacy_converters, builtin_legacy_converters),
defined_in_files(return_converters, builtin_return_converters))
- else:
+ elif not ns.list:
report_changes(writer, diff=ns.diff)
diff --git a/Tools/clinic/libclinic/dsl_parser.py
b/Tools/clinic/libclinic/dsl_parser.py
index 75924dcc05a750..b241f58711e68a 100644
--- a/Tools/clinic/libclinic/dsl_parser.py
+++ b/Tools/clinic/libclinic/dsl_parser.py
@@ -1396,184 +1396,10 @@ def state_function_docstring(self, line: str) -> None:
def format_docstring_signature(
f: Function, parameters: list[Parameter]
) -> str:
- lines = []
- lines.append(f.displayname)
- if f.forced_text_signature:
- lines.append(f.forced_text_signature)
- elif f.kind in ACCESSORS:
- # @getter and @setter do not need signatures like a method or a
function.
- return ''
- else:
- lines.append('(')
-
- # populate "right_bracket_count" field for every parameter
- assert parameters, "We should always have a self parameter. " +
repr(f)
- assert isinstance(parameters[0].converter, self_converter)
- # self is always positional-only.
- assert parameters[0].is_positional_only()
- assert parameters[0].right_bracket_count == 0
- positional_only = True
- for p in parameters[1:]:
- if not p.is_positional_only():
- positional_only = False
- else:
- assert positional_only
- if positional_only:
- p.right_bracket_count = p.group_depth
- else:
- # don't put any right brackets around non-positional-only
parameters, ever.
- p.right_bracket_count = 0
-
- right_bracket_count = 0
- last_group = 0
-
- def fix_right_bracket_count(desired: int, group: int = 0) -> str:
- nonlocal right_bracket_count, last_group
- s = ''
- if (group != last_group and right_bracket_count and
- ((desired >= right_bracket_count) if group < 0 else
- (desired <= right_bracket_count))):
- # The group is not nested in the previous group,
- # close the brackets of the latter first.
- s += ']' * right_bracket_count
- right_bracket_count = 0
- last_group = group
- while right_bracket_count < desired:
- s += '['
- right_bracket_count += 1
- while right_bracket_count > desired:
- s += ']'
- right_bracket_count -= 1
- return s
-
- need_slash = False
- added_slash = False
- need_a_trailing_slash = False
-
- # we only need a trailing slash:
- # * if this is not a "docstring_only" signature
- # * and if the last *shown* parameter is
- # positional only
- if not f.docstring_only:
- for p in reversed(parameters):
- if not p.converter.show_in_signature:
- continue
- if p.is_positional_only():
- need_a_trailing_slash = True
- break
-
-
- added_star = False
-
- first_parameter = True
- last_p = parameters[-1]
- line_length = len(''.join(lines))
- indent = " " * line_length
- def add_parameter(text: str) -> None:
- nonlocal line_length
- nonlocal first_parameter
- if first_parameter:
- s = text
- first_parameter = False
- else:
- s = ' ' + text
- if line_length + len(s) >= 72:
- lines.extend(["\n", indent])
- line_length = len(indent)
- s = text
- line_length += len(s)
- lines.append(s)
-
- for p in parameters:
- if not p.converter.show_in_signature:
- continue
- assert p.name
-
- is_self = isinstance(p.converter, self_converter)
- if is_self and f.docstring_only:
- # this isn't a real machine-parsable signature,
- # so let's not print the "self" parameter
- continue
-
- if p.is_positional_only():
- need_slash = not f.docstring_only
- elif need_slash and not (added_slash or
p.is_positional_only()):
- added_slash = True
- add_parameter('/,')
-
- if p.is_keyword_only() and not added_star:
- added_star = True
- add_parameter('*,')
-
- p_lines = [fix_right_bracket_count(p.right_bracket_count,
- p.group)]
-
- if isinstance(p.converter, self_converter):
- # annotate first parameter as being a "self".
- #
- # if inspect.Signature gets this function,
- # and it's already bound, the self parameter
- # will be stripped off.
- #
- # if it's not bound, it should be marked
- # as positional-only.
- #
- # note: we don't print "self" for __init__,
- # because this isn't actually the signature
- # for __init__. (it can't be, __init__ doesn't
- # have a docstring.) if this is an __init__
- # (or __new__), then this signature is for
- # calling the class to construct a new instance.
- p_lines.append('$')
-
- if p.is_vararg():
- p_lines.append("*")
- added_star = True
- if p.is_var_keyword():
- p_lines.append("**")
-
- name = p.converter.signature_name or p.name
- p_lines.append(name)
-
- if not p.is_variable_length() and p.converter.is_optional():
- p_lines.append('=')
- value = p.converter.py_default
- if not value:
- value = repr(p.converter.default)
- p_lines.append(value)
-
- if (p != last_p) or need_a_trailing_slash:
- p_lines.append(',')
-
- p_output = "".join(p_lines)
- add_parameter(p_output)
-
- lines.append(fix_right_bracket_count(0))
- if need_a_trailing_slash:
- add_parameter('/')
- lines.append(')')
-
- # PEP 8 says:
- #
- # The Python standard library will not use function annotations
- # as that would result in a premature commitment to a particular
- # annotation style. Instead, the annotations are left for users
- # to discover and experiment with useful annotation styles.
- #
- # therefore this is commented out:
- #
- # if f.return_converter.py_default:
- # lines.append(' -> ')
- # lines.append(f.return_converter.py_default)
-
- if not f.docstring_only:
- lines.append("\n" + libclinic.SIG_END_MARKER + "\n")
-
- signature_line = "".join(lines)
-
- # now fix up the places where the brackets look wrong
- return signature_line.replace(', ]', ',] ')
-
+ signature = render_text_signature(f, parameters)
+ if signature and not f.docstring_only:
+ signature += "\n" + libclinic.SIG_END_MARKER + "\n"
+ return signature
@staticmethod
def format_docstring_parameters(params: list[Parameter]) -> str:
"""Create substitution text for {parameters}"""
@@ -1715,3 +1541,191 @@ def do_post_block_processing_cleanup(self, lineno: int)
-> None:
exc.lineno = lineno
exc.filename = self.clinic.filename
raise
+
+
+def render_text_signature(
+ f: Function,
+ parameters: list[Parameter],
+ *,
+ name: str | None = None,
+ line_width: int | None = 72,
+) -> str:
+ """Render the text signature of the function.
+
+ *name* replaces the name of the function. *line_width* is the width
+ at which the signature is wrapped, None disables wrapping.
+ """
+ lines = []
+ lines.append(f.displayname if name is None else name)
+ if f.forced_text_signature:
+ lines.append(f.forced_text_signature)
+ elif f.kind in ACCESSORS:
+ # @getter and @setter do not need signatures like a method or a
function.
+ return ''
+ else:
+ lines.append('(')
+
+ # populate "right_bracket_count" field for every parameter
+ assert parameters, "We should always have a self parameter. " + repr(f)
+ assert isinstance(parameters[0].converter, self_converter)
+ # self is always positional-only.
+ assert parameters[0].is_positional_only()
+ assert parameters[0].right_bracket_count == 0
+ positional_only = True
+ for p in parameters[1:]:
+ if not p.is_positional_only():
+ positional_only = False
+ else:
+ assert positional_only
+ if positional_only:
+ p.right_bracket_count = p.group_depth
+ else:
+ # don't put any right brackets around non-positional-only
parameters, ever.
+ p.right_bracket_count = 0
+
+ right_bracket_count = 0
+ last_group = 0
+
+ def fix_right_bracket_count(desired: int, group: int = 0) -> str:
+ nonlocal right_bracket_count, last_group
+ s = ''
+ if (group != last_group and right_bracket_count and
+ ((desired >= right_bracket_count) if group < 0 else
+ (desired <= right_bracket_count))):
+ # The group is not nested in the previous group,
+ # close the brackets of the latter first.
+ s += ']' * right_bracket_count
+ right_bracket_count = 0
+ last_group = group
+ while right_bracket_count < desired:
+ s += '['
+ right_bracket_count += 1
+ while right_bracket_count > desired:
+ s += ']'
+ right_bracket_count -= 1
+ return s
+
+ need_slash = False
+ added_slash = False
+ need_a_trailing_slash = False
+
+ # we only need a trailing slash:
+ # * if this is not a "docstring_only" signature
+ # * and if the last *shown* parameter is
+ # positional only
+ if not f.docstring_only:
+ for p in reversed(parameters):
+ if not p.converter.show_in_signature:
+ continue
+ if p.is_positional_only():
+ need_a_trailing_slash = True
+ break
+
+
+ added_star = False
+
+ first_parameter = True
+ last_p = parameters[-1]
+ line_length = len(''.join(lines))
+ indent = " " * line_length
+ def add_parameter(text: str) -> None:
+ nonlocal line_length
+ nonlocal first_parameter
+ if first_parameter:
+ s = text
+ first_parameter = False
+ else:
+ s = ' ' + text
+ if line_width is not None and line_length + len(s) >=
line_width:
+ lines.extend(["\n", indent])
+ line_length = len(indent)
+ s = text
+ line_length += len(s)
+ lines.append(s)
+
+ for p in parameters:
+ if not p.converter.show_in_signature:
+ continue
+ assert p.name
+
+ is_self = isinstance(p.converter, self_converter)
+ if is_self and f.docstring_only:
+ # this isn't a real machine-parsable signature,
+ # so let's not print the "self" parameter
+ continue
+
+ if p.is_positional_only():
+ need_slash = not f.docstring_only
+ elif need_slash and not (added_slash or p.is_positional_only()):
+ added_slash = True
+ add_parameter('/,')
+
+ if p.is_keyword_only() and not added_star:
+ added_star = True
+ add_parameter('*,')
+
+ p_lines = [fix_right_bracket_count(p.right_bracket_count,
+ p.group)]
+
+ if isinstance(p.converter, self_converter):
+ # annotate first parameter as being a "self".
+ #
+ # if inspect.Signature gets this function,
+ # and it's already bound, the self parameter
+ # will be stripped off.
+ #
+ # if it's not bound, it should be marked
+ # as positional-only.
+ #
+ # note: we don't print "self" for __init__,
+ # because this isn't actually the signature
+ # for __init__. (it can't be, __init__ doesn't
+ # have a docstring.) if this is an __init__
+ # (or __new__), then this signature is for
+ # calling the class to construct a new instance.
+ p_lines.append('$')
+
+ if p.is_vararg():
+ p_lines.append("*")
+ added_star = True
+ if p.is_var_keyword():
+ p_lines.append("**")
+
+ name = p.converter.signature_name or p.name
+ p_lines.append(name)
+
+ if not p.is_variable_length() and p.converter.is_optional():
+ p_lines.append('=')
+ value = p.converter.py_default
+ if not value:
+ value = repr(p.converter.default)
+ p_lines.append(value)
+
+ if (p != last_p) or need_a_trailing_slash:
+ p_lines.append(',')
+
+ p_output = "".join(p_lines)
+ add_parameter(p_output)
+
+ lines.append(fix_right_bracket_count(0))
+ if need_a_trailing_slash:
+ add_parameter('/')
+ lines.append(')')
+
+ # PEP 8 says:
+ #
+ # The Python standard library will not use function annotations
+ # as that would result in a premature commitment to a particular
+ # annotation style. Instead, the annotations are left for users
+ # to discover and experiment with useful annotation styles.
+ #
+ # therefore this is commented out:
+ #
+ # if f.return_converter.py_default:
+ # lines.append(' -> ')
+ # lines.append(f.return_converter.py_default)
+
+ signature_line = "".join(lines)
+
+ # now fix up the places where the brackets look wrong
+ return signature_line.replace(', ]', ',] ')
diff --git a/Tools/clinic/libclinic/function.py
b/Tools/clinic/libclinic/function.py
index 58b61c6f822196..af858f2e40e75e 100644
--- a/Tools/clinic/libclinic/function.py
+++ b/Tools/clinic/libclinic/function.py
@@ -282,6 +282,36 @@ def render_docstring(self) -> str:
ParamTuple = tuple["Parameter", ...]
+Definition = Module | Class | Function
+
+
+def walk_definitions(
+ parent: Clinic | Module | Class,
+ prefix: str = '',
+ depth: int = 0,
+) -> Iterator[tuple[int, str, Definition]]:
+ """Yield (depth, dotted name, definition) for every nested definition.
+
+ The name of a module is already fully qualified, but the name of
+ a class is not, hence the prefix.
+ """
+ for function in parent.functions:
+ if function.kind.new_or_init:
+ # __new__() and __init__() are called as the class itself.
+ name = prefix
+ else:
+ name = f'{prefix}.{function.name}' if prefix else function.name
+ yield depth, name, function
+ for cls in parent.classes.values():
+ name = f'{prefix}.{cls.name}' if prefix else cls.name
+ yield depth, name, cls
+ yield from walk_definitions(cls, name, depth + 1)
+ if not isinstance(parent, Class):
+ # Only a module can contain modules.
+ for module in parent.modules.values():
+ yield depth, module.name, module
+ yield from walk_definitions(module, module.name, depth + 1)
+
def permute_left_option_groups(
l: Sequence[Iterable[Parameter]]
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]