-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
PEP 646: Add section on grammar changes #2039
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
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
2e40622
PEP 646: Add section on grammar changes
mrahtz af4c363
PEP 646: Clarify how unpacked TypeVarTuples would behave
mrahtz 5a9f67d
PEP 646: Further clarify behaviour of *args: *Ts
mrahtz e0bd2a1
PEP 646: Simplify explanation of how *args: *Ts behaces
mrahtz f3bf402
PEP 646: Fix first set of Guido's suggestions
mrahtz 53bb98b
PEP 646: Rewrite description of grammar change semantics to be clearer
mrahtz c656fab
PEP 646: Fix Guido's second set of suggestions
mrahtz c9236e2
PEP 646: Clarify which alternative is new in the grammar changes for …
mrahtz 3502def
PEP 646: Final tweaks suggested by Guido
mrahtz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -733,28 +733,208 @@ hopefully enable a thriving ecosystem of tools for analysing and verifying | |
shape properties of numerical computing programs. | ||
|
||
|
||
Backwards Compatibility | ||
======================= | ||
Grammar Changes | ||
=============== | ||
|
||
This PEP requires two grammar changes. Full diffs of ``python.gram`` | ||
and simple tests to confirm correct behaviour are available at | ||
https://github.com/mrahtz/cpython/commits/pep646-grammar. | ||
|
||
In order to use the star operator for unpacking of ``TypeVarTuple`` instances, | ||
we would need to make two grammar changes: | ||
Star expressions in indexes | ||
--------------------------- | ||
|
||
1. Star expressions must be made valid in at least index operations. | ||
For example, ``Tuple[*Ts]`` and ``Tuple[T1, *Ts, T2]`` would both | ||
be valid. (This PEP does not allow multiple unpacked ``TypeVarTuple`` | ||
instances to appear in a single parameter list, so ``Tuple[*Ts1, *Ts2]`` | ||
would be a runtime error. Also note that star expressions would *not* | ||
be valid in slice expressions - e.g. ``Tuple[*Ts:*Ts]`` is | ||
nonsensical and should remain invalid.) | ||
2. We would need to make '``*args: *Ts``' valid in function definitions. | ||
The first grammar change enables use of star expressions in index operations (that is, | ||
within square brackets), necessary to support star-unpacking of TypeVarTuples: | ||
|
||
In both cases, at runtime the star operator would call ``Ts.__iter__()``. | ||
This would, in turn, return an instance of a helper class, e.g. | ||
``UnpackedTypeVarTuple``, whose ``repr`` would be ``*Ts``. | ||
:: | ||
|
||
If these grammar changes are considered too burdensome, we could instead | ||
simply use ``Unpack`` - though in this case it might be better for us to | ||
first decide whether there's a better option. | ||
DType = TypeVar('DType') | ||
Shape = TypeVarTuple('Shape') | ||
class Array(Generic[DType, *Shape]): | ||
... | ||
|
||
Before: | ||
|
||
:: | ||
|
||
slices: | ||
| slice !',' | ||
| ','.slice+ [','] | ||
|
||
After: | ||
|
||
:: | ||
|
||
slices: | ||
| slice !',' | ||
| ','.(slice | starred_expression)+ [','] | ||
|
||
As with star-unpacking in other contexts, the star operator calls ``__iter__`` | ||
on the callee, and adds the contents of the resulting iterator to the argument | ||
passed to ``__getitem__``. For example, if we do ``foo[a, *b, c]``, and | ||
``b.__iter__`` produces an iterator yielding ``d`` and ``e``, | ||
``foo.__getitem__`` would receive ``(a, d, e, c)``. | ||
|
||
To put it another way, note that ``x[..., *a, ...]`` produces the same result | ||
as ``x[(..., a*, ...)]``` (with any slices ``i:j`` in ``...`` replaced with | ||
``slice(i, j)``, with the one edge case that ``x[*a]`` becomes ``x[(*a,)]``). | ||
|
||
TypeVarTuple Implementation | ||
''''''''''''''''''''''''''' | ||
|
||
With this grammar change, ``TypeVarTuple`` is implemented as follows. | ||
Note that this implementation is useful only for the benefit of a) correct | ||
``repr()`` and b) runtime analysers; static analysers would not use the | ||
implementation. | ||
|
||
:: | ||
|
||
class TypeVarTuple: | ||
def __init__(self, name): | ||
self._name = name | ||
self._unpacked = UnpackedTypeVarTuple(name) | ||
def __iter__(self): | ||
yield self._unpacked | ||
def __repr__(self): | ||
return self._name | ||
|
||
class UnpackedTypeVarTuple: | ||
def __init__(self, name): | ||
self._name = name | ||
def __repr__(self): | ||
return '*' + self._name | ||
|
||
Implications | ||
'''''''''''' | ||
|
||
This grammar change implies a number of additional changes in behaviour not | ||
required by this PEP. We choose to allow these additional changes rather than | ||
disallowing them at a syntax level in order to keep the syntax change as small | ||
as possible. | ||
|
||
First, the grammar change enables star-unpacking of other structures, such | ||
as lists, within indexing operations: | ||
|
||
:: | ||
|
||
idxs_to_select = (1, 2) | ||
array[0, *idxs_to_select, -1] # Equivalent to [0, 1, 2, -1] | ||
|
||
Second, more than one instance of a star-unpack can occur within an index: | ||
|
||
:: | ||
|
||
array[*idxs_to_select, *idxs_to_select] # Equivalent to array[1, 2, 1, 2] | ||
|
||
Note that this PEP disallows multiple unpacked TypeVarTuples within a single | ||
type parameter list. This requirement would therefore need to be implemented | ||
in type checking tools themselves rather than at the syntax level. | ||
|
||
Third, slices may co-occur with starred expressions: | ||
|
||
:: | ||
|
||
array[3:5, *idxs_to_select] # Equivalent to array[3:5, 1, 2] | ||
|
||
However, note that slices involving starred expressions are still invalid: | ||
|
||
:: | ||
|
||
# Syntax error | ||
array[*idxs_start:*idxs_end] | ||
|
||
|
||
``*args`` as a TypeVarTuple | ||
--------------------------- | ||
|
||
The second change enables use of ``*args: *Ts`` in function definitions. | ||
|
||
Before: | ||
|
||
:: | ||
|
||
star_etc: | ||
| '*' param_no_default param_maybe_default* [kwds] | ||
| '*' ',' param_maybe_default+ [kwds] | ||
| kwds | ||
|
||
After: | ||
|
||
:: | ||
|
||
star_etc: | ||
| '*' param_no_default param_maybe_default* [kwds] | ||
| '*' param_no_default_star_annotation param_maybe_default* [kwds] # New | ||
| '*' ',' param_maybe_default+ [kwds] | ||
| kwds | ||
|
||
Where: | ||
|
||
:: | ||
|
||
param_no_default_star_annotation: | ||
| param_star_annotation ',' TYPE_COMMENT? | ||
| param_star_annotation TYPE_COMMENT? &')' | ||
|
||
param_star_annotation: NAME star_annotation | ||
|
||
star_annotation: ':' star_expression | ||
|
||
This accomplishes the desired outcome (making ``*args: *Ts`` not be a syntax | ||
error) while matching the behaviour of star-unpacking in other contexts: | ||
at runtime, ``__iter__`` is called on the starred object, and a tuple | ||
containing the items of the resulting iterator is set as the type annotion | ||
for ``args``. In other words, at runtime ``*args: *foo`` is equivalent to | ||
``*args: tuple(foo)``. | ||
|
||
:: | ||
|
||
>>> Ts = TypeVarTuple('Ts') | ||
>>> def foo(*args: *Ts): pass # Equivalent to `*args: tuple(Ts)` | ||
>>> foo.__annotations__ | ||
{'args': (*Ts,)} | ||
# *Ts is the repr() of Ts._unpacked, an instance of UnpackedTypeVarTuple | ||
|
||
Note that the only scenario in which this grammar change allows ``*Ts`` to be | ||
used as a direct annotation (rather than being wrapped in e.g. ``Tuple[*Ts]``) | ||
is ``*args``. Other uses are still invalid: | ||
|
||
:: | ||
|
||
x: *Ts # Syntax error | ||
def foo(x: *Ts): pass # Syntax error | ||
|
||
Implications | ||
'''''''''''' | ||
|
||
As with the first grammar change, this change also has a number of side effects. | ||
In particular, the annotation of ``*args`` could be set to a starred object | ||
other than a ``TypeVarTuple`` - for example, the following nonsensical | ||
annotation is possible: | ||
|
||
:: | ||
|
||
>>> foo = [1, 2, 3] | ||
>>> def bar(*args: *foo): pass # Equivalent to `*args: tuple(foo)` | ||
>>> bar.__annotations__ | ||
{'args': (1, 2, 3)} | ||
|
||
Again, prevention of such annotations will need to be done by, say, static | ||
checkers, rather than at the level of syntax. | ||
|
||
Alternatives | ||
------------ | ||
|
||
If these two grammar changes are considered too burdensome, there are two alternatives: | ||
|
||
1. **Support change 1 but not change 2**. Starred expressions within indexes are | ||
more important to us than the ability to annotation ``*args``. | ||
Comment on lines
+930
to
+931
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. |
||
2. **Use ``Unpack`` instead** - though in this case it might be better for us to | ||
reconsider our options to see whether there isn't another option which would be | ||
more readable. | ||
|
||
Backwards Compatibility | ||
======================= | ||
|
||
The ``Unpack`` version of the PEP should be back-portable to previous | ||
versions of Python. | ||
|
@@ -766,7 +946,6 @@ uses of the class will still work, and b) parameterised and unparameterised | |
versions of the class can be used together (relevant if, for example, library | ||
code is updated to use parameters while user code is not, or vice-versa). | ||
|
||
|
||
Reference Implementation | ||
======================== | ||
|
||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.