Is there a way to differentiate between a variable containing a plain string and one containing an expression, like an isexpression() function? For example how can I establish between these two:
The variable doesn’t contain the expression. It contains a value. In your test1 example, the value it contains is a pretty boring one - None - because the print() function doesn’t return anything useful.
I’m not sure what you’re trying to achieve, but if you’re trying to figure out whether something’s safe to evaluate, I would recommend looking at ast.literal_eval; it’s able to parse a lot of things that would be considered safe, but rejects anything with a function call in it. But do be aware that an f-string isn’t safe; in fact, there’s nothing stopping you from doing something like f'{aFunc("param1", "param2")}' and calling the function from inside the string.
But you may need to provide us with more context to figure out what it is you’re trying to do. Maybe a different form of string interpolation will work for you.
With hindsight using ast.parse is the obviously correct solution. Note that ast.parse is capable of fairly sophisticated operations, there shouldn’t be any need for string slicing. And your current code will tell you that '4' is a string, when I think you’ll want to interpret that as an int instead. (Then again, maybe you have a guarantee that it won’t ever be a string.)