Introduce 'expr' keyword

The OP does list 3 use cases: names in __all__, special namespace of metaclasses, and names in friendlier debugging messages.

Names in __all__ (and __slots__) are a legimate use case but can and are special-cased by all linters so they aren’t a real problem.

Special namespace of metaclasses is needed rather rarely so it isn’t a problem big enough to justify this new feature either.

Friendlier debugging messages can be produced with a function over an f-string or a t-string so it isn’t a problem either.

I think the missing bit is “getting expression string without evaluating it, while letting IDE know that it is expression”. Don’t think it is possible with f-strings. Is this possible with t-strings at any chance?

1 Like

But for what purpose would you ever want a debugging message to show an expression without evaluating it? Even the OP lists this use case with the expression explicitly evaluated.

Regarding debugging:

print(f'{foo()[0].name!e} evaluates to {foo()[0].name!r}')

I don’t want to evaluate it 2 times. The first one should just paste an input string, while my IDE recognises it as syntax and parser does few checks on its validity.

I had some use cases over time. Apart from debugging, one that I remember is synchronization of dict keys with variable names, so that I can do:

def foo(a):
    d = dict()
    d[f'{a!e}')] = a   # Or whatever solution

And my IDE allows me to refactor nicely.

Maybe I am missing something.

So this would implicitly evaluate A and B expressions and discard the results without any application?

I was talking about a helper debug function that takes an f-string:

def debug(expr_value):
    expr, _, value = expr_value.partition('=')
    print(f'{expr} evaluates to {value}')

debug(f'{1 + 1=}') # 1 + 1 evaluates to 2

But what kind of a real-world application were you creating this dict for?

By friendlier debugging messages I was referring to the use case #3 that the OP listed:

Ah ok, thanks.
This does seem to cut it for many cases nicely, but f'{expr!e}' would make it much more convenient and adaptive to situation. E.g.

d = something[0].attr    # type(dict)
...
...
...
print(f'{something[0].attr["key"]!e} has value {d["key"]!r}')

Found mail logs from that time. So 3 use cases from back then:

  1. logging
  2. dict with keys synced to variable names: [Python-ideas] Extract variable name from itself
  3. Things like: https://www.mail-archive.com/python-ideas@python.org/msg33011.html

Sorry, I was gone for a while. Others have already replied with some other use cases, thank you all for that!

Here is another example: A simplified version of code in my current codebase. This class is a node of a tree structure that is immutable (as you can see by the frozen and tuple children):

@dataclass(frozen=True)
class CollectionNode[T: SlotTypeT]:
    slot: T
    parent: 'CollectionNode[T] | None'
    children: tuple[CollectionNode[T], ...]

    def _post_set_children(self, children: tuple[CollectionNode[T], ...]) -> None:
        """Set the children field of the node.
        
        It is logically impossible to set both parent and children at
        construction time for a whole tree. Therefore we allow the 
        exception to postpone setting the children field.
        """
        object.__setattr__(self, 'children', children)

Instead of 'children', I could use expr('CollectionNode.children') or expr CollectionNode.children (whatever way we will implement it) here. If I ever change the attribute name, it would then give an error, or better yet, auto-rename the given expression.
Here this is especially meaningful that the expression is not evaluated, because it is technically just a type annotation on the class and only an actual attribute on an object. So if this is evaluated, it will cause an error.

1 Like

So this would implicitly evaluate A and B expressions and discard the results without any application?

No, if we talk about the original idea, using an expr keyword, it would return a tuple[str, T] if an equal sign (=) is at the end, otherwise it would simply return the exact expression as a string.
At the beginning I had some wrong assumptions that we could just have the interpreter check whether the expression would mention valid identifers if the expression is not also evaluated, which is not really possible and something I would not want add anymore. Checking if it is a valid expression and that all parts of the expression are statically available identifiers should be done by the linter/type checker/ide.

But I would step away from the expr keyword recommendation and instead opt for the previously mentioned expr function, that takes in a string, which can optionally be evaluted, but the string should be special cased by linters/type checkers/the ide and treated like it was an expression outside of a string.

Example:

class Foo:
    bar: str

expr('Foo.bar')  # returns 'Foo.bar'
expr('Foo.bar', true)  # raises AttributeError, because it is just a type hint on the class

Assuming we are in VSCode, the string 'Foo.bar' should be highlighted as if it was outside of a string and therefore should also be affected by renaming, similar to type hints in the ForwardRef format (wrapped in a string).

1 Like