44

How can I (easily) take a string such as "sin(x)*x^2" which might be entered by a user at runtime and produce a Python function that could be evaluated for any value of x?

bummi
  • 27,123
  • 14
  • 62
  • 101
WalkingRandomly
  • 4,537
  • 5
  • 34
  • 34

7 Answers7

54

Python's own internal compiler can parse this, if you use Python notation.

If your change the notation slightly, you'll be happier.

import compiler
eq= "sin(x)*x**2"
ast= compiler.parse( eq )

You get an abstract syntax tree that you can work with.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • 1
    Can you add a sample code on how to use the derived function? – Don May 09 '11 at 10:49
  • 2
    @Don: You don't need to use the syntax tree. Use the original function. `eval("sin(x)*x**2")` after setting `x` and using `from math import *`. – S.Lott May 09 '11 at 11:05
  • 1
    eval() works, but the answer doesn't actually solve the problem :( – James Broadhead Feb 02 '12 at 15:44
  • @JamesBroadhead: How does `eval()` not solve the problem? Can you explain what's missing? "evaluated for any value" seems to be covered by `eval`. What's missing? – S.Lott Feb 02 '12 at 16:18
  • It's not in the Answer ;). Ideally, you'd edit the answer to point that out (and the various problems associated with compiler.parse() and eval() that others have mentioned. – James Broadhead Feb 06 '12 at 17:45
  • "various problems associated with compiler.parse() and eval() that others have mentioned" seems redundant. Also. I asked the wrong question about your comment. "the answer doesn't actually solve the problem"? What more should be there -- outside of `eval()` which I specifically tried to avoid putting in the answer. – S.Lott Feb 06 '12 at 18:07
  • 2
    `import compiler` doesn't work with Python 3.3 Could you please update your answer + tags to make sure the reader know which one it is about. It's a good question and should have the relevant tags covered. – ha9u63a7 Nov 12 '14 at 21:58
  • 5
    @hagubear the replacement of the `compiler` module for python 3 is described here: http://stackoverflow.com/questions/909092/why-is-the-compiler-package-discontinued-in-python-3 – Andre Holzner Nov 18 '14 at 20:55
  • 8
    Do NOT use eval with data coming from an external source (e.g. web) as that is an easy attack vector. – Solomon Ucko Jan 09 '18 at 17:50
  • 4
    The compiler module is deprecated since Python 2.6. This answer is obsolete – Olivier Melançon Jun 08 '19 at 18:21
34

EDIT parser is deprecated in Python 3.9: https://docs.python.org/3/whatsnew/3.9.html#new-parser

You can use Python parser:

import parser
from math import sin

formula = "sin(x)*x**2"
code = parser.expr(formula).compile()
x = 10
print(eval(code))

It performs better than pure eval and, of course, avoids code injection!

Don
  • 16,928
  • 12
  • 63
  • 101
  • 7
    One important thing to note is that using pure `eval()` on user input can be [**very dangerous**](https://stackoverflow.com/questions/1832940/is-using-eval-in-python-a-bad-practice/1832957#1832957). – user Mar 03 '16 at 17:48
  • @devxeq I mean that it won't accept such things as `formula = "os.system('format C:')"` :) – Don Apr 26 '17 at 12:23
  • 1
    @Don Fair enough. :) – devxeq May 03 '17 at 09:50
  • 11
    I'm not sure how this is any better than using eval on the string itself. It will still happily execute all code, not just formulas. Including bad things like os.system calls. `>>> eval(parser.expr("os.system('echo evil syscall')").compile())` `evil syscall` – Peter Jun 02 '18 at 08:59
  • 1
    parser is deprecated in python 3.9 https://docs.python.org/3/whatsnew/3.9.html#new-parser – user3225309 Jan 04 '22 at 21:22
15
 f = parser.parse('sin(x)*x^2').to_pyfunc()

Where parser could be defined using PLY, pyparsing, builtin tokenizer, parser, ast.

Don't use eval on user input.

jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • 7
    `eval` on user input is indeed bad. – igauravsehrawat Sep 08 '16 at 15:42
  • Could you be more specific? I have tried to import `parser` module in Python 3.8 but it complains: `Module 'parser' has no attribute 'parse'` – s.ouchene Sep 16 '21 at 09:30
  • @s.ouchene: "parser" here is just a stand-in for a custom module that implements the corresponding functionality (it is not literally stdlib's `parser` module). Here's [an example for arithmetic expressions](https://stackoverflow.com/a/9558001/4279) – jfs Sep 16 '21 at 17:57
9

pyparsing might do what you want (http://pyparsing.wikispaces.com/) especially if the strings are from an untrusted source.

See also http://pyparsing.wikispaces.com/file/view/fourFn.py for a fairly full-featured calculator built with it.

cryo
  • 14,219
  • 4
  • 32
  • 35
3

To emphasize J.F. Sebastian's advice, 'eval' and even the 'compiler' solutions can be open to subtle security holes. How trustworthy is the input? With 'compiler' you can at least filter out things like getattr lookups from the AST, but I've found it's easier to use PLY or pyparsing for this sort of thing than it is to secure the result of letting Python help out.

Also, 'compiler' is clumsy and hard to use. It's deprecated and removed in 3.0. You should use the 'ast' module (added in 2.6, available in 2.5 as '_ast').

Andrew Dalke
  • 14,889
  • 4
  • 39
  • 54
1

In agreement with vartec. I would use SymPy - in particular the lambdify function should do exactly what you want.

See: http://showmedo.com/videotutorials/video?name=7200080&fromSeriesID=720

for a very nice explanation of this.

Best wishes,

Geddes
  • 1,191
  • 2
  • 11
  • 25
  • 4
    Please include all the relevant information in the post itself, *"Here is a link to a video that answers the question"* is not a good answer. – Baum mit Augen Apr 25 '16 at 14:01
0

Sage is intended as matlab replacement and in intro videos it's demonstrated how similar to yours cases are handled. They seem to be supporting a wide range of approaches. Since the code is open-source you could browse and see for yourself how the authors handle such cases.

SilentGhost
  • 307,395
  • 66
  • 306
  • 293