Meta
This is a continuation post of Introduce 'expr' keyword - #28 by JoniKauf .
It should have been clear to me from the start that introducing a new keyword for the proposed idea is not a viable solution.
I’m opening this new topic because the original expr
keyword idea has been eliminated almost instantly and because it was the main point of that topic, I think starting over in a new topic about an actually possible implementation is more organized.
The second reason is because this is now a way more baked idea ( Meta: Lifetime of an idea. 1, 2 & 3 ) and the possible list of implementations has been stripped down immensely until I could finally decide for a final proposal.
I hope these are valid enough reasons for opening a second topic.
Finally:
Thank you all so much for all the feedback and suggestions in the other topic. There were a lot of ideas and many of those were very nice solutions!
On-topic
I looked at all suggestions and have two favourites I would like to focus this topic on. The first on is my actual proposal, the second one is an honourable mention that doesn’t quite cut it. If something is not discussed here, check out the previous topic!
The proposal: An expr
function
First off, I feel a bit lousy for seeing all these good suggestions and picking mine as the ‘best’. But I think in terms of what the goal is, this is the only solution that sets out to fix all issues mentioned below.
The problem
Sometimes we need to turn an expression (most often identifiers) into the format how it is written in-code, which will from now on be called its expressive
form. The current solutions and their issues are:
- Copy the expression into a string manually
Issue: Code duplication, which is dangerous and tedious when code is refactored to not be accidentally forgotten - Use the f-string debug syntax
f'{expr=}.rpartition('=')[2]'
Issue: Cannot be fully generalized by a function and worst of all: evaluates the given expression, no matter how expensive - Use my 3.14 way mentioned here by using template strings.
Issue: Still evaluates the expression.
Solution requirements
- Lazily turn an expression into its expressive form
- Reduce code duplication
- Do so in a concise way
- IDE tools, like renaming, should work nicely with it
- Other obvious goals, like backwards compatibility, non disruptiveness… (they will not be further mentioned, as they should be obvious)
The proposal:
Add the expr
function into builtins
. The function definition would look something like this:
from typing import Any, Literal, LiteralString, overload
_EvalResult = Any # Placeholder, should be the result of eval inferred by the type checker
@overload
def expr[T: LiteralString](e: T, /) -> T:
@overload
def expr[T: LiteralString](e: T, evaluate: Literal[False], /) -> str:
@overload
def expr[T: LiteralString](e: T, evaluate: Literal[True], /) -> tuple[T, _EvalResult]: ...
def expr[T: LiteralString](e: T, evaluate: bool = False, /) -> T | tuple[T, _EvalResult]:
if evaluate:
return (e, eval(e))
return e
This function would need to be special cased by linters, type checkers and more sophisticated than my 3 lines of code:
-
Linters would need to understand that
e
is an expression and it should be treated like it was outside of a string, so renaming and other features would work like in other parts of the code. You can imagine it similarly to how types hints wrapped in strings are often also treated specially (for forward refs). -
Type checkers would need to understand that the returned tuple’s second value, when
eval
is true, is the expression in the string evaluated, like if it was outside of a string. Optionally, the string passed -
When
eval
is true, we want to evaluate the expression. The problem is that in many scenarios we would requireglobals()
andlocals()
to be passed. This would be rather clunky. I do not know, but assume, this is possible to implement in C such that it is a special function that automatically takes these in.
Goal completion
Looking at all proposed solutions, this is the only one that covers all issues.
It is:
- short
- has no duplication
- is lazy
- can relatively easily be supported by the type checkers, linters and IDEs
The only real issue I could see is the support from outside tools needed. Similar constructs, like types wrapped in strings, are already supported though, therefore I think this should not be an issue.
Escaping inner strings of the expression should not be an issue, because other wrapper quotation marks could just be chosen and it is highly unlikely that all four possible string definition formats ("
, '
, """
, '''
) are used in a single expression. If absolutely required, escape sequences of strings could just be detected by the tools as well.
Why expr
as a name
I follow the naming convention of the exec
and eval
function:
exec
: Execute statementseval
: Evaluate an expressionexpr
: Turn an expression into its expressive form and optionallyeval
it.
They all have in common that they can execute a string as Python code (though the others can also execute code), so I think this would be a nice fit for the name.
This is my proposal. Following up is an honourable mention and is not part of it technically, but could have potential to still be fixed somehow (awaiting ideas).
An almost perfect solution: !e
conversion
My favourite idea by somebody else was proposed by @dg-pb:
The !e
conversion syntax that could be introduced to f-strings (post: Introduce 'expr' keyword - #19 by dg-pb ).
Example:
>>> f"{int('10')!e}"
>>> int('10')
Compared to other solutions it would be a minor language change, it has no backwards incompatibility (to my knowledge) and I just think using a conversion just feels like it fits perfectly for the use case. Obviously, the other conversions (a
for ascii, s
for str and r
for repr) turn the value of the expression into the specified format, not the expression itself, but I still think it fits nicely and feels like a natural solution.
But sadly there is a major problem with this solution, which is that the expression cannot be evaluated at the same time too, so if the value and the string is needed as well, one has to repeat the expression, which defeats the entire purpose of this proposal.