-2

Is there any way to convert a string with variables into an operation??

For example

a = 3
b = 7

str = 'a * b'

# *MAGIC SOLUTION TO TURN IT INTO AN OPERATION*

print (str) # Prints 21

Please, don't give others solutions for the problem, because I know it's very easy to do by a diferent way. But I really need it for a harder problem

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • 1
    You might be interested in [sympy](http://www.sympy.org/en/index.html). ([Examples in docs](http://docs.sympy.org/latest/tutorial/basic_operations.html), look for Symbol, sympify and subs) – jedwards Jun 08 '18 at 02:54
  • 5
    Fair warning, any answer suggesting `eval` will be unconditionally downvoted. – cs95 Jun 08 '18 at 02:56
  • @coldspeed why though? beginner here – pyeR_biz Jun 08 '18 at 02:57
  • 2
    @Poppinyoshi https://stackoverflow.com/questions/1832940/why-is-using-eval-a-bad-practice – user3483203 Jun 08 '18 at 02:59
  • 2
    @What does this mean? `Please, don't give others solutions for the problem, because I know it's very easy to do by a diferent way` – cs95 Jun 08 '18 at 03:01
  • `eval(str)` is probably the only fully general solution, but it's generally a bad idea. If you can limit the kinds of expressions you allow, you can write a parser for it. – Barmar Jun 08 '18 at 03:01
  • 1
    How do we know which "other solutions" to not provide? If you know of some solutions, what is wrong with them that is causing you to look for something different? – Bryan Oakley Jun 08 '18 at 03:02
  • If your expression language is a strict subset of Python, you can call `ast.parse`, then walk the tree and interpreting the expression. That way you handle exactly the limited subset of Python that you want to, so it works for evaluating `a * b`, but not for `__import__('os').system('rm -rf ~')` or `print(SECRET_KEY)` or any other expression you don’t want to evaluate. If it’s not a struct subset of Python, you’ll need to write a parser (or maybe find a pre-made one that does exactly what you want in the PyParsing examples or similar). – abarnert Jun 08 '18 at 03:08
  • @coldspeed What if I call `exec` to assign that expression to a global variable and then return the contents of that variable? :) – abarnert Jun 08 '18 at 03:11
  • 1
    @abarnert you know I'd make an exception for an abarnert answer... ;-) – cs95 Jun 08 '18 at 03:21
  • @coldspeed Yeah, but using `exec` really just the same thing as `eval`, only more complicated and even more insecure. – abarnert Jun 08 '18 at 03:26

4 Answers4

4

You can do this safely by using in-built operator package, and using globals

from operator import sub, add, truediv, mul
import re

def string_eval(str_):
     operators = {'-': sub, '+': add, '/': truediv, '*': mul}
     pattern = re.compile(r'(.*?)\s*([*\/+-])\s*(.*)')  
     splited = re.search(pattern, str_).groups()
     if len(splited) == 3:
         a, ope, b = splited
         val_a = globals()[a]
         val_b = globals()[b]
         return operators[ope](val_a, val_b)

Now for this to work you have to declare the variables first

>>> a = 45
>>> b = 25
>>> string_eval('a - b')
20
>>> string_eval('a * b')
1125
Bijoy
  • 1,131
  • 1
  • 12
  • 23
  • You got ahead of me. I was thinking about the same thing, except I would suggest that you use `re` to find operators or variables. upvoted – AGN Gazer Jun 08 '18 at 03:52
  • This is about as minimal a parser and interpreter as you could imagine… and yet it's enough for at least the OP's example. Nice. – abarnert Jun 08 '18 at 03:52
  • 2
    One question: why are you using `get` instead of `[]`? That may prevent `KeyError` exceptions, but it's only going to get you a `TypeError` instead (either from trying to call `None`, or trying to pass `None` to an operator function), and I don't think that's any clearer to the user. – abarnert Jun 08 '18 at 03:54
  • `pat = re.compile(r"\*|/|\+|-")`; `op = re.search(pat, 'a * b').group(0)`; `var1, var2 = list(map(str.strip, pat.split('a * b')))` Maybe others could suggest better ways of doing things – AGN Gazer Jun 08 '18 at 03:55
  • @abarnert, ya you are right, but as m checking `ope in operators`, that wont be a problem i guess. – Bijoy Jun 08 '18 at 03:58
  • @AGNGazer Why not use `(.*?)\s*([*\/\\+])\s*(.*)` so you don't need two separate regex calls and then mapping strip? – abarnert Jun 08 '18 at 03:58
  • @Bijoy Yeah, but in that case it's even more strange to not use `[]`. Also, that way you just silently return `None` instead of raising any error at all. – abarnert Jun 08 '18 at 03:59
  • @abarnert great suggestion! Hopefully Bijoy will integrate it. Also I agree with you about []. – AGN Gazer Jun 08 '18 at 04:01
  • @AGNGazer thanks for the suggestion, edited with using `re` package. – Bijoy Jun 08 '18 at 04:56
  • @abarnert Is there a nice way of dropping empty strings? or is list comprehension inevitable? – AGN Gazer Jun 08 '18 at 04:57
  • @AGNGazer The point of the `\s*` between the capture groups is to allow but ignore whitespace around the operator. (As an untested regex, I wouldn't be surprised if it didn't actually work exactly as intended, but that's the idea… and now that I think about it, making the operands be identifier characters instead of `.` means you could avoid all the greedy-vs.-lazy stuff that I may have gotten wrong…) Is that what you mean by "dropping empty strings"? – abarnert Jun 08 '18 at 05:01
  • 1
    @AGNGazer Somewhat surprisingly, [it looks like I did get it right](https://regex101.com/r/jnvbpw/1). :) (EDIT: no I didn't, but thanks to Bijoy [now I think I did](https://regex101.com/r/jnvbpw/3)…) – abarnert Jun 08 '18 at 05:02
  • 2
    @abarnert add `-` too after `+`, then it works well for add, sub, mul and div. – Bijoy Jun 08 '18 at 05:02
  • @Bijoy Oops, yeah, you probably want that… and take out the `\\`. – abarnert Jun 08 '18 at 05:03
  • @abarnet Alright... For a moment I thought I was going cuckoo. Thanks Bijoy – AGN Gazer Jun 08 '18 at 05:04
  • How does this code work when 'a' is 0? – AGN Gazer Jun 08 '18 at 05:06
  • 1
    You really need to switch to `[]` as @abarnert suggested. Better crash than return `None` – AGN Gazer Jun 08 '18 at 05:07
  • 1
    lol that was a nice loophole, got rid of `get()`. – Bijoy Jun 08 '18 at 05:17
  • @Bijoy PERFECT. Really i don't undertand completely how it works but the functions do what is supossed to. Thank you so much! – Lisandro Dalla Costa Jun 08 '18 at 21:57
3

In Python 3.6+, you can use literal string interpolation.

a = 3
b = 7

print(f'{a * b}') # prints: '21'
Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73
3

I'm assuming "don't give others solutions for the problem, because I know it's very easy to do by a diferent way" is referring to eval as the easy answer you don't want.

But really, what you want is almost certainly a function that parses and interprets an expression in some language, just like eval does, it's just that you want a simpler and more restricted language than Python, maybe just basic arithmetic expressions.

As Olivier Melançon's answer shows, Python also comes with an interpreter for a limited subset of Python in the f-string evaluator, and as jedwards' answer shows, there are readily-available interpreters for what are effectively similar languages to such a subset.


But if you want to handle exactly the language you want in exactly the way you want, you have to do it yourself. And it's not actually as scary as it sounds. I'll use Python's own ast module for parsing, but then write my own interpreter. This works as long as you really want a strict subset of Python, but if you want something different—e.g., ^ for exponentiation (which has the wrong precedence and associativity), or variables that aren't valid Python identifiers, etc., you'll have to build the parser too.

import ast
import operator

ops = {
    ast.Add: operator.add, 
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    ast.Div: operator.truediv,
}
def interpret(expr, env):
    if isinstance(expr, ast.Expr):
        return interpret(expr.value, env)
    elif isinstance(expr, ast.BinOp):
        op = ops[type(expr.op)]
        return op(interpret(expr.left, env), interpret(expr.right, env))
    elif isinstance(expr, ast.Num):
        return expr.n
    elif isinstance(expr, ast.Name):
        val = env[expr.id]
        if isinstance(val, (bool, int, float, complex)):
            return val
        raise ValueError(val)
    else:
        raise ValueError(expr)
def evaluate(s, env=None):
    if env is None: env = globals()
    tree = ast.parse(s)
    return interpret(tree.body[0], env)

Now, you can evaluate anything using the basic 4 arithmetic operators, parentheses, numeric constants, or the names of global variables whose values are numbers, but nothing else:

>>> a = 3
>>> b = 7
>>> evaluate('a * b')
21
>>> evaluate('3*a + 2*(b+10)')
32
>>> evaluate('__import__("os")')
ValueError: <_ast.Call object at 0x109210da0>
>>> evaluate('a * b', {'a': 10, 'b': 1j})
10j
>>> evaluate('-2')
ValueError: <_ast.UnaryOp object at 0x1092fc5c0>

Obviously the error handling could be nicer, and I probably should have handled at least unary - in case you want negative numbers… but this should be enough to get the point across. And reading the ast docs linked above, it should be clear how to extend this further.


On the other hand, Bijoy's answer is an even simpler parser and interpreter. It won't handle more complicated expressions—but if you don't want it to, that's a feature, not a bug.

abarnert
  • 354,177
  • 51
  • 601
  • 671
1

Using the sympy library (docs) is one approach:

from sympy import Symbol, sympify

# Define symbols
a = Symbol('a')
b = Symbol('b')

# Parse the string into an equation
eqn = sympify('a * b')

# Substitute values for variables
eqn = eqn.subs(a, 3)
eqn = eqn.subs(b, 7)

print(eqn) # Prints 21

Or, even closer to what you had written (but I like it considerably less):

from sympy import sympify

a = 3
b = 7

# Parse the string into an equation
eqn = sympify('a * b', locals = locals())

print(eqn) # Prints 21

EDIT NOTE: Obligatory mention of disappointment, sympify, the string-to-equation parsing command, using eval(). :sad face:

That being said, sympy is a helpful library worth considering, and you can certainly build an equation without using sympify.

jedwards
  • 29,432
  • 3
  • 65
  • 92