PEP 646: Explain the results of the new grammar/compiler change (#2189)
This commit is contained in:
parent
641e39fa70
commit
27b476d1c6
69
pep-0646.rst
69
pep-0646.rst
|
@ -871,9 +871,7 @@ shape properties of numerical computing programs.
|
||||||
Grammar Changes
|
Grammar Changes
|
||||||
===============
|
===============
|
||||||
|
|
||||||
This PEP requires two grammar changes. Full diffs of ``python.gram``
|
This PEP requires two grammar changes.
|
||||||
and simple tests to confirm correct behaviour are available at
|
|
||||||
https://github.com/mrahtz/cpython/commits/pep646-grammar.
|
|
||||||
|
|
||||||
Change 1: Star Expressions in Indexes
|
Change 1: Star Expressions in Indexes
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
@ -1015,21 +1013,58 @@ Where:
|
||||||
|
|
||||||
star_annotation: ':' star_expression
|
star_annotation: ':' star_expression
|
||||||
|
|
||||||
This accomplishes the desired outcome (making ``*args: *Ts`` not be a syntax
|
We also need to deal with the ``star_expression`` that results from this
|
||||||
error) while matching the behaviour of star-unpacking in other contexts:
|
construction. Normally, a ``star_expression`` occurs within the context
|
||||||
at runtime, ``__iter__`` is called on the starred object, and a tuple
|
of e.g. a list, so a ``star_expression`` is handled by essentially
|
||||||
containing the items of the resulting iterator is set as the type annotion
|
calling ``iter()`` on the starred object, and inserting the results
|
||||||
for ``args``. In other words, at runtime ``*args: *foo`` is equivalent to
|
of the resulting iterator into the list at the appropriate place. For
|
||||||
``*args: tuple(foo)``.
|
``*args: *Ts``, however, we must process the ``star_expression`` in a
|
||||||
|
different way.
|
||||||
|
|
||||||
|
We do this by instead making a special case for the ``star_expression``
|
||||||
|
resulting from ``*args: *Ts``, emitting code equivalent to
|
||||||
|
``[annotation_value] = [*Ts]``. That is, we create an iterator from
|
||||||
|
``Ts`` by calling ``Ts.__iter__``, fetch a single value from the iterator,
|
||||||
|
verify that the iterator is exhausted, and set that value as the annotation
|
||||||
|
value. This results in the unpacked ``TypeVarTuple`` being set directly
|
||||||
|
as the runtime annotation for ``*args``:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
>>> Ts = TypeVarTuple('Ts')
|
>>> Ts = TypeVarTuple('Ts')
|
||||||
>>> def foo(*args: *Ts): pass # Equivalent to `*args: tuple(Ts)`
|
>>> def foo(*args: *Ts): pass
|
||||||
>>> foo.__annotations__
|
>>> foo.__annotations__
|
||||||
{'args': (*Ts,)}
|
{'args': *Ts}
|
||||||
# *Ts is the repr() of Ts._unpacked, an instance of UnpackedTypeVarTuple
|
# *Ts is the repr() of Ts._unpacked, an instance of UnpackedTypeVarTuple
|
||||||
|
|
||||||
|
This allows the runtime annotation to be consistent with an AST representation
|
||||||
|
that uses a ``Starred`` node for the annotations of ``args`` - in turn important
|
||||||
|
for tools that rely on the AST such as mypy to correctly recognise the construction:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
>>> print(ast.dump(ast.parse('def foo(*args: *Ts): pass'), indent=2))
|
||||||
|
Module(
|
||||||
|
body=[
|
||||||
|
FunctionDef(
|
||||||
|
name='foo',
|
||||||
|
args=arguments(
|
||||||
|
posonlyargs=[],
|
||||||
|
args=[],
|
||||||
|
vararg=arg(
|
||||||
|
arg='args',
|
||||||
|
annotation=Starred(
|
||||||
|
value=Name(id='Ts', ctx=Load()),
|
||||||
|
ctx=Load())),
|
||||||
|
kwonlyargs=[],
|
||||||
|
kw_defaults=[],
|
||||||
|
defaults=[]),
|
||||||
|
body=[
|
||||||
|
Pass()],
|
||||||
|
decorator_list=[])],
|
||||||
|
type_ignores=[])
|
||||||
|
|
||||||
|
|
||||||
Note that the only scenario in which this grammar change allows ``*Ts`` to be
|
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]``)
|
used as a direct annotation (rather than being wrapped in e.g. ``Tuple[*Ts]``)
|
||||||
is ``*args``. Other uses are still invalid:
|
is ``*args``. Other uses are still invalid:
|
||||||
|
@ -1045,14 +1080,18 @@ Implications
|
||||||
As with the first grammar change, this change also has a number of side effects.
|
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
|
In particular, the annotation of ``*args`` could be set to a starred object
|
||||||
other than a ``TypeVarTuple`` - for example, the following nonsensical
|
other than a ``TypeVarTuple`` - for example, the following nonsensical
|
||||||
annotation is possible:
|
annotations are possible:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
>>> foo = [1, 2, 3]
|
>>> foo = [1]
|
||||||
>>> def bar(*args: *foo): pass # Equivalent to `*args: tuple(foo)`
|
>>> def bar(*args: *foo): pass
|
||||||
>>> bar.__annotations__
|
>>> bar.__annotations__
|
||||||
{'args': (1, 2, 3)}
|
{'args': 1}
|
||||||
|
|
||||||
|
>>> foo = [1, 2]
|
||||||
|
>>> def bar(*args: *foo): pass
|
||||||
|
ValueError: too many values to unpack (expected 1)
|
||||||
|
|
||||||
Again, prevention of such annotations will need to be done by, say, static
|
Again, prevention of such annotations will need to be done by, say, static
|
||||||
checkers, rather than at the level of syntax.
|
checkers, rather than at the level of syntax.
|
||||||
|
|
Loading…
Reference in New Issue