13

How can I allow users to execute mathematical expressions in a safe way? Do I need to write a full parser?

Is there something like ast.literal_eval(), but for expressions?

Ivo Danihelka
  • 3,382
  • 3
  • 31
  • 27
  • Voting to close because it is not clear what constitutes a "mathematical expression", or what kind of "parsing" is needed - i.e., what needs to be done with the result. In programming, `expression` normally has a specific, technical meaning related to the grammar of that language; in Python, `ast.literal_eval` evaluates a subset of expressions, while `eval` evaluates all expressions but unsafely. "In a safe way" is fundamentally in tension with general-purpose parsing; it is necessary to have **specific and clear** definitions of what is allowed, what is not, and what happens after parsing. – Karl Knechtel Sep 07 '22 at 03:29

5 Answers5

11

The examples provided with Pyparsing include several expression parsers:

If you want a more pre-packaged solution, look at plusminus, a pyparsing-based extensible arithmetic parsing package.

PaulMcG
  • 62,419
  • 16
  • 94
  • 130
3

What sort of expressions do you want? Variable assignment? Function evaluation?

SymPy aims to become a full-fledged Python CAS.

Katriel
  • 120,462
  • 19
  • 136
  • 170
  • I'm able to alter my needs based on the available possibilities. SymPy looks interesting. A small single-file library would be even better. – Ivo Danihelka Aug 27 '10 at 13:49
  • @Ivo: The possibilities depend on your needs! @Paul linked several simple ways of doing this with `pyparsing`. `SymPy` yields more power, at greater expense (large, may be slow). – Katriel Aug 27 '10 at 13:59
  • `SymPy` is not safe for untrusted input. – Luke Taylor Mar 11 '16 at 15:45
1

Few weeks ago I did similar thing, but for logical expressions (or, and, not, comparisons, parentheses etc.). I did this using Ply parser. I have created simple lexer and parser. Parser generated AST tree that was later use to perform calculations. Doing this in that way allow you to fully control what user enter, because only expressions that are compatible with grammar will be parsed.

Zuljin
  • 2,612
  • 17
  • 14
0

Yes. Even if there were an equivalent of ast.literal_eval() for expressions, a Python expression can be lots of things other than just a pure mathematical expression, for example an arbitrary function call.

It wouldn't surprise me if there's already a good mathematical expression parser/evaluator available out there in some open-source module, but if not, it's pretty easy to write one of your own.

jchl
  • 6,332
  • 4
  • 27
  • 51
-2

maths functions will consist of numeric and punctuation characters, possible 'E' or 'e' if you allow scientific notation for rational numbers, and the only (other) legal use of alpha characters will be if you allow/provide specific maths functions (e.g. stddev). So, should be trivial to run along the string for alpha characters and check the next little bit isn't suspicious, then simply eval the string in a try/except block.

Re the comments this reply has received... I agree this approach is playing with fire. Still, that doesn't mean it can't be done safely. I'm new to python (< 2 months), so may not know the workarounds to which this is vulnerable (and of course a new Python version could always render the code unsafe in the future), but - for what little it's worth (mainly my own amusement) - here's my crack at it:

def evalMaths(s):
    i = 0
    while i < len(s):
        while s[i].isalpha() and i < len(s):
            idn += s[i]
            i += 1
        if (idn and idn != 'e' and idn != 'abs' and idn != 'round'):
            raise Exception("you naughty boy: don't " + repr(idn))
        else:
            i += 1
    return eval(s)

I would be very interested to hear if/how it can be circumvented... (^_^) BTW / I know you can call functions like abs2783 or _983 - if they existed, but they won't. I mean something practical.

In fact, if anyone can do so, I'll create a question with 200 bounty and accept their answer.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • 3
    -1 Don't do this. `eval`-ing a user-input string is __never__ a good idea. Someone will find a way around your protection. – Katriel Aug 27 '10 at 08:32
  • How about providing a justification rather than a completely pointless assertion? It may not be pretty, but if you want to get something done, there's a cost-benefit analysis in not having a working solution, or fiddling with something more complex. – Tony Delroy Aug 27 '10 at 08:35
  • 1
    @Tony, the cost of messing this up is to infinity and beyond. – carl Aug 27 '10 at 09:03
  • 2
    I'm with Tony on this one. Engineering is about understanding the costs and benefits. True, there is a huge risk in using eval, but there are situations where it's appropriate. I have a command-line eval.py that I use for quick calculations. I am the user, so using eval is perfectly fine. You have to know what you are doing, but "never" is a strong word. – Ned Batchelder Aug 27 '10 at 10:11
  • 1
    @Ned: true that. I was thinking "untrusted user", in which case I would still say "never". – Katriel Aug 27 '10 at 10:41
  • @Tony: if `s` is not constrained to be a string: http://pastebin.com/tennNUx2. If it is, you can raise an error with `'round(round)'`; otherwise, I'm still thinking! – Katriel Aug 27 '10 at 11:12
  • 4
    @Tony: `eval( "()"*8**5 )` causes a segfault. – Katriel Aug 27 '10 at 11:17
  • @Ned: good point. Still, here, the question's clearly asking for something abuse proof. @Alex: round(round)'s ok - I always said to call this in a try/except block. Other's interesting: eval on the string '"()"*8**5' is safe, which is what the use might be able to enter. But, eval of an extremely long string may fault... if the user does a read without any size limit, then there's always vulnerability though isn't there, quite independent of the use of eval. So, would you agree this is a distinct I/O issue not an eval one? – Tony Delroy Aug 28 '10 at 05:45