Differentiate between a string and an expression

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:

test1 = 'print("hello")' # <- expression

test2 = '"print(\"hello\")"' # <- string

A clearer demonstration of one reason why I need to establish between a string and a function:

in_str = '"a {myVar} string"'
stored_code1 = 'f' + in_str
print(stored_code1)

in_str = 'aFunc("param1", "param2")'
stored_code2 = in_str
print(stored_code2)

def aFunc(par1, par2):
    return None

myVar = "test"
print(eval(stored_code1))
print(eval(stored_code2))

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.

@Rosuav you are quite right, question now edited to show test1 & test2 correctly. I will look intoast.literal_eval as you suggest.

The second example more clearly demonstrates why I need to establish the difference.

Thanks, @Rosuav that works with a try.. except block:

try:
    x = ast.literal_eval(in_str)
    print(in_str, "is string")
except:
    print(in_str, "is function")

However, not sure I like the idea of using try…except to determine a condition, that just feels like it should be used more for, well, exceptions.

Further to your suggestion of ast.literal_eval() I have now found a solution using ast.parse():

import ast

def get_type(in_str):
    ast_tree = ast.parse(in_str)
    if str(ast_tree.body[0].value)[:8] == "Constant":
        return "constant"
    if str(ast_tree.body[0].value)[:4] == "Call":
        return "function"

in_str = '"a {myVar} string"'
print(in_str, "is a",get_type(in_str))

in_str = 'aFunc("param1", "param2")'
print(in_str, "is a",get_type(in_str))

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

I couldn’t find any way to refer to the correct piece of the ast tree without using string slicing.

Would have been better if I had called the return values “constant” / “function”. Now edited, also moved in to a function.