Zing
Zing

Reputation: 113

How to substitute value of variables in Python expression, but not evaluate the expression?

I have a Python expression that looks like the following:

var1 = 'GOOGLE'
var2 = '5'
expr = 'df[df[var1]>=var2]'

In my workspace var1 and var2 are well defined so I can evaluate expr as follows:

eval(expr)

However, I want to pass this expr (as string) to another function with values of var1 and var2 substituted in it. I do not want to pass the variables var1 and var2, as I can have any number of variables, not just two. How do I accomplish this?

Upvotes: 2

Views: 500

Answers (3)

Ataxias
Ataxias

Reputation: 1193

The "proper" way would be to do use ast.parse, as @blhsing recommended. Still, in many cases this may be an overkill. In your simple case and similar cases, this is what I would do:

import re

expression = "3*x + function_x(y)"
values = {'x': 2, 'y': 5}  # feel free to use a subset of `globals()` or `locals()` here, to reflect your workspace

# Substitute variable symbols with their values
for var, val in values.items():
    pattern = r'\b' + re.escape(var) + r'\b'
    expression = re.sub(pattern, str(val), expression)
    
assert(expression == "3*2 + function_x(5)")

The r'\b' pattern ensures that you only match whole-word occurences, in case a variable exists as a substring of another variable or function/built-in name etc (like x here exists in function_x).

The re.escape() function is necessary when you have variable symbols that contain special characters that have a special meaning in regular expressions. For example, the variable symbol itself could contain a special character, such as a .: y.z.

Upvotes: 0

blhsing
blhsing

Reputation: 107124

You can parse the expression with ast.parse and use a subclass of ast.NodeTransformer to convert Name nodes to the corresponding values as Constant nodes, and then convert the AST back to code with ast.unparse:

import ast

var1 = 'GOOGLE'
var2 = '5'
expr = 'df[df[var1]>=var2]'

class NamesToConstants(ast.NodeTransformer):
    def visit_Name(self, node):
        if node.id in globals(): # feel free to use your own dict instead of globals()
            value = globals()[node.id]
            try: # convert value to integer if viable
                value = int(value)
            except:
                pass
            return ast.Constant(value=value)
        return node

tree = ast.parse(expr)
NamesToConstants().visit(tree)
print(ast.unparse(tree))

This outputs:

df[df['GOOGLE'] >= 5]

ast.unparse requires Python 3.10 or later. If you're using an earlier version, you can use astunparse.unparse from the astunparse package instead.

Demo: https://trinket.io/python3/18cc1182d0

Upvotes: 2

Maxwell D. Dorliea
Maxwell D. Dorliea

Reputation: 1322

You can simply use Python f-string as demonstrated below

expr = f'df[df[{var1}] >= {var2}]'

Upvotes: 1

Related Questions