@@ -414,53 +414,69 @@ except that all expressions are always implicitly wrapped with a ``lambda``:
414414 lexical scope, including local and global variables. Any valid Python expression
415415 can be used, including function and method calls.
416416
417- However, this PEP further proposes to extend the usual lexical scoping of a
418- lambda-wrapped expression in the interpolation, as seen with class definitions,
419- to that supported by `annotation scopes
420- <https://docs.python.org/3/reference/executionmodel.html#annotation-scopes> `_,
421- so as to minimize developer surprise.
417+ However, there's one additional nuance to consider, ` function scope
418+ <https://docs.python.org/3/reference/executionmodel.html#resolution-of-names> `_
419+ versus `annotation scope
420+ <https://docs.python.org/3/reference/executionmodel.html#annotation-scopes> `_.
421+ Consider this somewhat contrived example to configure captions:
422422
423- Let's look at why using annotation scope is important with a somewhat contrived
424- example where a developer sets up how figures are rendered in a caption, using a
425- ``html `` tag:
423+ .. code-block :: python
424+
425+ class CaptionConfig :
426+ tag = ' b'
427+ figure = f ' < { tag} >Figure</ { tag} > '
428+
429+ Let's now attempt to rewrite the above example to use tag strings:
426430
427431.. code-block :: python
428432
429- class SomeConfig :
433+ class CaptionConfig :
430434 tag = ' b'
431435 figure = html' <{tag} >Figure</{tag} >'
432436
433- Unless the ``html `` function fully evaluates all interpolations before the class
434- is constructed, a subsequent interpolation will fail with ``NameError: name
435- 'tag' is not defined ``. This means that the name ``tag `` is no longer available
436- in the usual lexical scope for lambdas. Contrast that scoping with what is seen
437- in using a f-string:
437+ Unfortunately, this rewrite doesn't work if using the usual lambda wrapping to
438+ implement interpolations, namely ``lambda: tag ``. When the interpolations are
439+ evaluated by the tag function, it will result in ``NameError: name 'tag' is not
440+ defined ``. The root cause of this name error is that ``lambda: tag `` uses function scope,
441+ and it's therefore not able to use the class definition where ``tag `` is
442+ defined.
443+
444+ Desugaring how the tag string could be evaluated will result in the same
445+ ``NameError `` even using f-strings; the lambda wrapping here also uses function
446+ scoping:
438447
439448.. code-block :: python
440449
441- class SomeConfig :
450+ class CaptionConfig :
442451 tag = ' b'
443- figure = f ' < { tag} >Figure</ { tag} > '
452+ figure = f ' < { ( lambda : tag)() } >Figure</ { ( lambda : tag)() } > '
444453
445- The class variable ``figure `` here does evaluate correctly with respect to
446- lexical scope, if at the risk of an HTML injection attack. The reason it
447- evaluates correctly is that the evaluation is always immediate. First the
448- expression for ``tag `` is evaluated; then for ``figure ``; and finally both
449- settings are passed into the dynamic construction of the class ``SomeConfig ``.
454+ For tag strings, getting such a ``NameError `` would be surprising. It would also
455+ be a rough edge in using tag strings in this specific case of working with class
456+ variables. After all, tag strings are supposed to support a superset of the
457+ capabilities of f-strings.
450458
451- Because tag strings are supposed to work like f-strings, but with more
452- capabilities, it's necessary to support annotation scope for the lambda-wrapped
453- expressions in interpolations.
459+ The solution is to use annotation scope for tag string interpolations. While the
460+ name "annotation scope" suggests it's only about annotations, it solves this
461+ problem by lexically resolving names in the class definition, such as ``tag ``,
462+ unlike function scope.
454463
455- But why is annotation scope used, given that this PEP is not at all about
456- annotations?
464+ .. note ::
465+
466+ The use of annotation scope means it's not possible to fully desugar
467+ interpolations into Python code. Instead it's as if one is writing
468+ ``interpolation_lambda: tag ``, not ``lambda: tag ``, where a hypothetical
469+ ``interpolation_lambda `` keyword variant uses annotation scope instead of
470+ the standard function scope.
471+
472+ This is more or less how the reference implementation implements this
473+ concept (but without creating a new keyword of course).
457474
458- *Annotation * scope (as of :pep: `649 ` and :pep: `695 `) provides the necessary
459- scoping semantics for names used in class definitions, thereby avoiding the
460- ``NameError `` above. In addition, the implementation of these two PEPs provide a
461- somewhat similar deferred execution model for annotations. However, this PEP and
462- its reference implementation only use the support for annotation scope; it's up
463- to the tag function to evaluate any interpolations.
475+ This PEP and its reference implementation therefore use the support for
476+ annotation scope. Note that this usage is a separable part from the
477+ implementation of :pep: `649 ` and :pep: `695 ` which provides a somewhat similar
478+ deferred execution model for annotations. Instead it's up to the tag function to
479+ evaluate any interpolations.
464480
465481With annotation scope in place, lambda-wrapped expressions in interpolations
466482then provide the usual lexical scoping seen with f-strings. So there's no need
@@ -520,7 +536,7 @@ the following, at the cost of losing some type specificity:
520536 def mytag (* args : str | tuple ) -> Any:
521537 ...
522538
523- A user might write a tag string as a split string :
539+ A user might write a tag string as follows :
524540
525541.. code-block :: python
526542
@@ -568,7 +584,14 @@ This is equivalent to:
568584
569585.. code-block :: python
570586
571- mytag(DecodedConcrete(r ' Hi, ' ), InterpolationConcrete(lambda : name, ' name' , ' s' , ' format_spec' ), DecodedConcrete(r ' !' ))
587+ mytag(DecodedConcrete(r ' Hi, ' ), InterpolationConcrete(lambda : name, ' name' ,
588+ ' s' , ' format_spec' ), DecodedConcrete(r ' !' ))
589+
590+ .. note ::
591+
592+ To keep it simple, this and subsequent desugaring omits an important scoping
593+ aspect in how names in interpolation expressions are resolved, specifically
594+ when defining classes. See `Interpolation Expression Evaluation `_.
572595
573596No Empty Decoded String
574597-----------------------
0 commit comments