@@ -414,53 +414,69 @@ except that all expressions are always implicitly wrapped with a ``lambda``:
414
414
lexical scope, including local and global variables. Any valid Python expression
415
415
can be used, including function and method calls.
416
416
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:
422
422
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:
426
430
427
431
.. code-block :: python
428
432
429
- class SomeConfig :
433
+ class CaptionConfig :
430
434
tag = ' b'
431
435
figure = html' <{tag} >Figure</{tag} >'
432
436
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:
438
447
439
448
.. code-block :: python
440
449
441
- class SomeConfig :
450
+ class CaptionConfig :
442
451
tag = ' b'
443
- figure = f ' < { tag} >Figure</ { tag} > '
452
+ figure = f ' < { ( lambda : tag)() } >Figure</ { ( lambda : tag)() } > '
444
453
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.
450
458
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.
454
463
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).
457
474
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.
464
480
465
481
With annotation scope in place, lambda-wrapped expressions in interpolations
466
482
then 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:
520
536
def mytag (* args : str | tuple ) -> Any:
521
537
...
522
538
523
- A user might write a tag string as a split string :
539
+ A user might write a tag string as follows :
524
540
525
541
.. code-block :: python
526
542
@@ -568,7 +584,14 @@ This is equivalent to:
568
584
569
585
.. code-block :: python
570
586
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 `_.
572
595
573
596
No Empty Decoded String
574
597
-----------------------
0 commit comments