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?
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.
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?
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.
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).