From 65c657de21e08aae816cb3d8a9ce12cd0f64a191 Mon Sep 17 00:00:00 2001 From: adam j hartz Date: Thu, 30 Oct 2025 08:55:10 -0400 Subject: [PATCH 1/2] addressing sc feedback: reframe rationale section around syntactic consistency --- peps/pep-0798.rst | 95 +++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/peps/pep-0798.rst b/peps/pep-0798.rst index 6dce36b9765..ecdee762179 100644 --- a/peps/pep-0798.rst +++ b/peps/pep-0798.rst @@ -97,33 +97,38 @@ equivalent to ``(x async for ait in aits() for x in ait)``. Rationale ========= -Combining iterable objects together into a single larger object is a common -task. One `StackOverflow post +Combining multiple iterable objects together into a single object is a common +task. For example, one `StackOverflow post `_ -asking about flattening a list of lists, for example, has been viewed 4.6 -million times. Despite this being a common operation, the options currently -available for performing it concisely require levels of indirection that can -make the resulting code difficult to read and understand. - -The proposed notation is concise (avoiding the use and repetition of auxiliary -variables) and, we expect, intuitive and familiar to programmers familiar with -both comprehensions and unpacking notation (see :ref:`pep798-examples` for -examples of code from the standard library that could be rewritten more clearly -and concisely using the proposed syntax). - -This proposal was motivated in part by a written exam in a Python programming -class, where several students used the notation (specifically the ``set`` -version) in their solutions, assuming that it already existed in Python. This -suggests that the notation is intuitive, even to beginners. By contrast, the -existing syntax ``[x for it in its for x in it]`` is one that students often -get wrong, the natural impulse for many students being to reverse the order of -the ``for`` clauses. - -Additionally, the comment section of a `Reddit post +asking about flattening a list of lists has been viewed 4.6 million times, and +there are several examples of code from the standard library that perform this +operation (see :ref:`pep798-examples`). While Python provides a means of +combining a small, known number of iterables using extended unpacking from +:pep:`448`, no comparable syntax currently exists for combining an arbitrary +number of iterables. + +This proposal represents a natural extension of the language, paralleling +existing syntactic structures: where ``[x, y, z]`` creates a list from a fixed +number of vaues, ``[item for item in items]`` creates a list from an arbitrary +number of values; this proposal extends that notion to the construction of +lists that involve unpacking, making ``[*item for item in items]`` analogous to +``[*x, *y, *z]``. + +We expect this syntax to be intuitive and familiar to programmers already +comfortable with both comprehensions and unpacking notation. This proposal was +motivated in part by a written exam in a Python programming class, where +several students used the proposed notation (specifically the ``set`` version) +in their solutions, assuming that it already existed in Python. This suggests +that the notation represents a logical, consistent extension to Python's +existing syntax. By contrast, the existing double-loop version ``[x for it in +its for x in it]`` is one that students often get wrong, the natural impulse +for many students being to reverse the order of the ``for`` clauses. The +intuitiveness of the proposed syntax is further supported by the comment +section of a `Reddit post `__ -following the publication of this PEP shows substantial support for the -proposal and further suggests that the syntax proposed here is legible, -intuitive, and useful. +made following the initial publication of this PEP, which demonstrates support +from a broader community. + Specification ============= @@ -412,9 +417,9 @@ Code Examples ============= This section shows some illustrative examples of how small pieces of code from -the standard library could be rewritten to make use of this new syntax to -improve concision and readability. The :ref:`pep798-reference` continues to -pass all tests with these replacements made. +the standard library could be rewritten to make use of this new syntax. The +:ref:`pep798-reference` continues to pass all tests with these replacements +made. Replacing Explicit Loops ------------------------ @@ -430,7 +435,7 @@ need for defining and referencing an auxiliary variable. comments.extend(token.comments) return comments - # improved: + # proposed: return [*token.comments for token in self] * From ``shutil.py``:: @@ -441,7 +446,7 @@ need for defining and referencing an auxiliary variable. ignored_names.extend(fnmatch.filter(names, pattern)) return set(ignored_names) - # improved: + # proposed: return {*fnmatch.filter(names, pattern) for pattern in patterns} * From ``http/cookiejar.py``:: @@ -452,7 +457,7 @@ need for defining and referencing an auxiliary variable. cookies.extend(self._cookies_for_domain(domain, request)) return cookies - # improved: + # proposed: return [ *self._cookies_for_domain(domain, request) for domain in self._cookies.keys() @@ -463,8 +468,8 @@ Replacing from_iterable and Friends While not always the right choice, replacing ``itertools.chain.from_iterable`` and ``map`` can avoid an extra level of redirection, resulting in code that -follows conventional wisdom that comprehensions are more readable than -map/filter. +follows conventional wisdom that comprehensions are generally more readable +than map/filter. * From ``dataclasses.py``:: @@ -473,7 +478,7 @@ map/filter. itertools.chain.from_iterable(map(_get_slots, cls.__mro__[1:-1])) ) - # improved: + # proposed: inherited_slots = {*_get_slots(c) for c in cls.__mro__[1:-1]} * From ``importlib/metadata/__init__.py``:: @@ -483,7 +488,7 @@ map/filter. path.search(prepared) for path in map(FastPath, paths) ) - # improved: + # proposed: return (*FastPath(path).search(prepared) for path in paths) * From ``collections/__init__.py`` (``Counter`` class):: @@ -491,7 +496,7 @@ map/filter. # current: return _chain.from_iterable(_starmap(_repeat, self.items())) - # improved: + # proposed: return (*_repeat(elt, num) for elt, num in self.items()) * From ``zipfile/_path/__init__.py``:: @@ -499,7 +504,7 @@ map/filter. # current: parents = itertools.chain.from_iterable(map(_parents, names)) - # improved: + # proposed: parents = (*_parents(name) for name in names) * From ``_pyrepl/_module_completer.py``:: @@ -510,7 +515,7 @@ map/filter. for spec in specs if spec )) - # improved: + # proposed: search_locations = { *getattr(spec, 'submodule_search_locations', []) for spec in specs if spec @@ -520,14 +525,14 @@ Replacing Double Loops in Comprehensions ---------------------------------------- Replacing double loops in comprehensions avoids the need for defining and -referencing an auxiliary variable, reducing clutter. +referencing an auxiliary variable. * From ``importlib/resources/readers.py``:: # current: children = (child for path in self._paths for child in path.iterdir()) - # improved: + # proposed: children = (*path.iterdir() for path in self._paths) * From ``asyncio/base_events.py``:: @@ -535,7 +540,7 @@ referencing an auxiliary variable, reducing clutter. # current: exceptions = [exc for sub in exceptions for exc in sub] - # improved: + # proposed: exceptions = [*sub for sub in exceptions] * From ``_weakrefset.py``:: @@ -543,7 +548,7 @@ referencing an auxiliary variable, reducing clutter. # current: return self.__class__(e for s in (self, other) for e in s) - # improved: + # proposed: return self.__class__(*s for s in (self, other)) @@ -817,8 +822,8 @@ this syntax was clear and intuitive, several concerns and potential downsides were raised as well. This section aims to summarize those concerns. * **Overlap with existing alternatives:** - While the proposed syntax is arguably clearer and more concise, there are - already several ways to accomplish this same thing in Python. + While the proposed syntax is arguably more concise, there are already several + ways to accomplish this same thing in Python. * **Function call ambiguity:** Expressions like ``f(*x for x in y)`` may initially appear ambiguous, as it's @@ -828,7 +833,7 @@ were raised as well. This section aims to summarize those concerns. may not be immediately obvious. * **Potential for overuse or abuse:** - Complex uses of unpacking in comprehensions could obscure logic that would be + Complex uses of unpacking in comprehensions could obscure logic that may be clearer in an explicit loop. While this is already a concern with comprehensions more generally, the addition of ``*`` and ``**`` may make particularly complex uses even more difficult to read and understand at a From ca4699e5619c2e7f4b4a8f279d88317d91fe8de0 Mon Sep 17 00:00:00 2001 From: adam j hartz Date: Thu, 30 Oct 2025 08:58:25 -0400 Subject: [PATCH 2/2] additional phrasing changes --- peps/pep-0798.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/peps/pep-0798.rst b/peps/pep-0798.rst index ecdee762179..400aa6f3097 100644 --- a/peps/pep-0798.rst +++ b/peps/pep-0798.rst @@ -778,9 +778,9 @@ Beyond the proposal outlined above, the following were also considered: This strategy would also make unpacking in synchronous and asynchronous generators behave symmetrically, but it would also be more complex, enough so that the cost may not be worth the benefit. As such, this PEP proposes - that generator expressions using the unpacking operator should not use - semantics similar to ``yield from`` until ``yield from`` is supported in - asynchronous generators more generally. + that asynchronous generator expressions using the unpacking operator should + not adopt semantics similar to ``yield from`` until ``yield from`` is + supported in asynchronous generators more generally. 3. Using ``yield from`` for unpacking in synchronous generator expressions, and disallowing unpacking in asynchronous generator expressions until they @@ -822,8 +822,9 @@ this syntax was clear and intuitive, several concerns and potential downsides were raised as well. This section aims to summarize those concerns. * **Overlap with existing alternatives:** - While the proposed syntax is arguably more concise, there are already several - ways to accomplish this same thing in Python. + While the proposed syntax represents a consistent extension to the language + and is likely to result in more-concise code, there are already several ways + to accomplish this same thing in Python. * **Function call ambiguity:** Expressions like ``f(*x for x in y)`` may initially appear ambiguous, as it's @@ -839,7 +840,7 @@ were raised as well. This section aims to summarize those concerns. particularly complex uses even more difficult to read and understand at a glance. For example, while these situations are likely rare, comprehensions that use unpacking in multiple ways can make it difficult to know what's - being unpacked and when: ``f(*(*x for *x, _ in list_of_lists))``. + being unpacked and when, e.g., ``f(*(*x for *x, _ in list_of_lists))``. * **Unclear limitation of scope:** This proposal restricts unpacking to the top level of the comprehension