1

Given the following math function in form of a Python function:

import math

def f(x):
    a = x - math.log(x)
    b = x + math.log(x)
    return a / x + b / math.log(x)

Is there any way that I can convert this function into a string like

expr = '(x - math.log(x)) / x + (x + math.log(x)) / math.log(x)'

so that when I want to call the function, I can simply use it by

func = lambda x: eval(expr)
print(func(3))
# 4.364513583657809

Note that I want to keep a and b in the original function. In reality, I have a lot more intermediate variables. Also, I am aware sympy could do similar tasks, but I would like to know if it is possible to convert the function to string, as it would be much more efficient to store.

Any suggestions?

Shaun Han
  • 2,676
  • 2
  • 9
  • 29
  • store? store where? Saving lambda functions inside variables is not good practise (as you probably know). And you can have a lambda function with multiple parameters, so what's the issue here? – Mahrkeenerh Nov 11 '21 at 20:20
  • [but eval is really dangerous....](https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html) and yes, maybe you're writing all the code that gets `eval`ed, but do you know that you write perfect code? – Joshua Voskamp Nov 11 '21 at 20:21
  • Could you give a better idea of intent/desired usecase involving `a` and `b`? – Joshua Voskamp Nov 11 '21 at 20:23
  • How do you expect it to work if the function has control structures like loops? – Barmar Nov 11 '21 at 20:24
  • @JoshuaVoskamp Why does it matter? If they don't use `eval()` and just call the function, the same code imperfections will exist. – Barmar Nov 11 '21 at 20:25
  • 1
    `eval()` is only dangerous if you're evaluating source code you don't control. – Barmar Nov 11 '21 at 20:25
  • 2
    This sounds like an X-Y problem. It sounds like your *actual* problem is how to store functions. Instead of storing it as a source-code string, which is fraught with problems, consider using an approach like: https://stackoverflow.com/a/6234683/5014455 – juanpa.arrivillaga Nov 11 '21 at 20:27
  • 1
    @Barmar I hear your point; trying to debug an error on an `eval` would be misleading, at best, if it's executing code you wrote somewhere else. – Joshua Voskamp Nov 11 '21 at 20:29
  • 2
    Or better yet, use the [dill](https://pypi.org/project/dill/) package that implements function object serialization. – juanpa.arrivillaga Nov 11 '21 at 20:30
  • @juanpa.arrivillaga The unfortunate thing is, I need to save these function expressions as dictionary keys, which doesn't allow many dtypes – Shaun Han Nov 11 '21 at 20:37
  • @ShaunHan huh? Dictionaries don't *have* dtypes ... I think you mean types. And dictionaries can take arbitrarly many types as long as it is hashable. In any case `dill` will produce a `bytes`, which will be perfectly hashable – juanpa.arrivillaga Nov 11 '21 at 20:47
  • 1
    Perhaps this - https://stackoverflow.com/questions/427453/how-can-i-get-the-source-code-of-a-python-function – dgumo Nov 11 '21 at 21:24

2 Answers2

2

You're probably looking for a symbolic equation solver!

Sympy's lambdify feature can do this for you!

>>> fn = sympy.lambdify("x", '(x - log(x)) / x + (x + log(x)) / log(x)')
>>> fn(x=3)
4.364513583657809

Caution: this also uses eval internally as @Joshua Voskamp warns about in a comment

ti7
  • 16,375
  • 6
  • 40
  • 68
  • 1
    He said he knows he can do something similar in sympy. – Barmar Nov 11 '21 at 20:22
  • But my question is given the Python function, how can I convert it into the string `'(x - log(x)) / x + (x + log(x)) / log(x)'`? – Shaun Han Nov 11 '21 at 20:24
  • Ah, the haze of excitement makes reading difficult, still, I suspect from their wording that they may not be aware of Sympy's lambdify feature! – ti7 Nov 11 '21 at 20:24
  • 2
    But where does the string argument to `lambdify` come from? They just have a function. – Barmar Nov 11 '21 at 20:26
2

Your function is already a string the moment you write it to a file!

If the function is valid Python, you can then just import it

from myfile import expr
print(expr(3))  # 4.364513583657809

WARNING Do not ever do this

If you want some incredibly evil logic for some reason, you can save your function directly with inspect.getsource(f) and then do something like this

>>> fn_body = """def f(x):
...     a = x - math.log(x)
...     b = x + math.log(x)
...     return a / x + b / math.log(x)
... """
>>> eval(f'lambda {fn_body.split("(")[1].split(")")[0]}, _={exec(fn_body)}: {fn_body.split(" ", 1)[-1].split(")")[0]})')(3)
4.364513583657809

This works by finding the parts needed to call the function, evaluating the source as one of the args (to smuggle it into your namespace), and then building an anonymous function to call it

Further Caveats

  • not remotely maintainable
  • extremely fragile
  • will clobber or conflict with an existing function with the same name depending on use
  • you will still need to import math or whatever other libraries
  • won't work with default args without more pain
  • calling eval() first (before creating the lambda) will allow you to use inspect to get the signature (.signature()) and you can combine it with re and/or ast for a much robust parser, but a 1-liner seemed more exciting
  • manages to use both eval() and exec() for an extra helping of evil
ti7
  • 16,375
  • 6
  • 40
  • 68
  • Thanks. This is exactly what I'm looking for and it works fine. I just want to write some hard-coded scripts to do a certain task at hand, so it is perfectly fine as long as it works. – Shaun Han Nov 11 '21 at 21:44