3

I would like to output a user input expression to a string.

The reason is that the input expression is user defined. I want to output the result of the expression, and print the statement which lead to this result.

import sys
import shutil  

expression1 = sys.path
expression2 = shutil.which

def get_expression_str(expression):
    if callable(expression):
        return expression.__module__ +'.'+ expression.__name__
    else:
        raise TypeError('Could not convert expression to string')

#print(get_expression_str(expression1))
# returns : builtins.TypeError: Could not convert expression to string
#print(get_expression_str(expression2))
# returns : shutil.which

#print(str(expression1))
#results in a list like ['/home/bernard/clones/it-should-work/unit_test', ...  ,'/usr/lib/python3/dist-packages']

#print(repr(expression1))
#results in a list like ['/home/bernard/clones/it-should-work/unit_test', ...  ,'/usr/lib/python3/dist-packages']

I looked into the Python inspect module but even

inspect.iscode(sys.path)

returns False

For those who wonder why it is the reverse of a string parsed to an expression using functools.partial see parse statement string

Background.

A program should work. Should, but it not always does. Because a program need specific resources, OS, OS version, other packages, files, etc. Every program needs different requirements (resources) to function properly. Which specific requirement are needed can not be predicted. The system knows best which resources are and are not available. So instead of manually checking all settings and configurations let a help program do this for you.

So the user, or developer of a program, specify his requirements together with statements how to to retrieve this information : expressions. Which could be executed using eval. Could. Like mentioned on StackOverflow eval is evil. Use of eval is hard to make secure using a blacklist, see : http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html Using multiple tips of SO I use a namedtuple, with a string, to compare with the user input string, and a function.

A white-list is better then a blacklist. Only if the parsed expression string match a "bare_expression" then an expression is returned. This white-list contains more information how to process f.e. the "unit_of_measurement" . It goes to far to explain what and why, but this is needed. The list of the namedtuples is much more then just a white-list and is defined :

Expr_UOfM = collections.namedtuple('Expr_UOfM', ['bare_expression', 'keylist', 'function', 'unit_of_measurement', 'attrlist'])

The namedtuple which match a (very limited) list:

Exp_list = [Expr_UOfM('sys.path', '' , sys.path, un.STR, []),
            Expr_UOfM('shutil.which', '', shutil.which, None, [])] 

This list may be very long and the content is crucial for further correct processing. Note the first and third field are very similar. There should be a single point of reference, but for me, this is on this moment not possible. Note the string : 'sys.path' is equal to (a part of) the user input, and the expression : sys.path is part of the namedtuple list. A good separation, limiting possible abuse. If the string and the expression are not 100% identical weird behavior may occur which is very hard to debug. So it want using the get_expression_str function check if the first and third field are identical. Just for total robustness of the program.

I use Python 3.4

14
  • 1
    try str(expression) Commented Jun 17, 2015 at 13:26
  • I did so, see the 4e line from the bottom, but without success. Commented Jun 17, 2015 at 13:28
  • So where is the user's input? Aren't you starting with a string (e.g. 'sys.path')? Commented Jun 17, 2015 at 13:30
  • @jonrsharpe, yes I do. But it is stored in a NamedTuple as function (part) so it can be reused. I want to print (using XML) the content of the NamedTuple verbose to inspect on possible errors. Commented Jun 17, 2015 at 13:38
  • Could you provide a bit more context? As it stands, it's not at all clear what you're trying to do and why. Commented Jun 17, 2015 at 13:38

2 Answers 2

1

You could use inspect.getsource() and wrap your expression in a lambda. Then you can get an expression with this function:

def lambda_to_expr_str(lambda_fn):
    """c.f. https://stackoverflow.com/a/52615415/134077"""
    if not lambda_fn.__name__ == "<lambda>":
        raise ValueError('Tried to convert non-lambda expression to string')
    else:
        lambda_str = inspect.getsource(lambda_fn).strip()
        expression_start = lambda_str.index(':') + 1
        expression_str = lambda_str[expression_start:].strip()
        if expression_str.endswith(')') and '(' not in expression_str:
            # i.e. l = lambda_to_expr_str(lambda x: x + 1) => x + 1)
            expression_str = expression_str[:-1]
        return expression_str

Usage:

$ lambda_to_expr_str(lambda: sys.executable)
> 'sys.executable'

OR

$ f = lambda: sys.executable
$ lambda_to_expr_str(f)
> 'sys.executable'

And then eval with

$ eval(lambda_to_expr_str(lambda: sys.executable))
> '/usr/bin/python3.5'

Note that you can take parameters with this approach and pass them with the locals param of eval.

$ l = lambda_to_expr_str(lambda x: x + 1)  #  now l == 'x + 1'
$ eval(l, None, {'x': 1})
> 2

Here be Dragons. There are many pathological cases with this approach:

$ l, z = lambda_to_expr_str(lambda x: x + 1), 1234
$ l
> 'x + 1), 1234'

This is because inspect.getsource gets the entire line of code the lambda was declared on. Getting source of functions declared with def would avoid this problem, however passing a function body to eval is not possible as there could be side effects, i.e. setting variables, etc... Lambda's can produce side effects as well in Python 2, so even more dragons lie in pre-Python-3 land.

Sign up to request clarification or add additional context in comments.

Comments

0

Why not use eval?

>>> exp1 = "sys.path"
>>> exp2 = "[x*x for x in [1,2,3]]"
>>> eval(exp1)
['', 'C:\\Python27\\lib\\site-packages\\setuptools-0.6c11-py2.7.egg', 'C:\\Pytho
n27\\lib\\site-packages\\pip-1.1-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\
django_celery-3.1.1-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\south-0.8.4-p
y2.7.egg', 'C:\\Windows\\system32\\python27.zip', 'C:\\Python27\\DLLs', 'C:\\Pyt
hon27\\lib', 'C:\\Python27\\lib\\plat-win', 'C:\\Python27\\lib\\lib-tk', 'C:\\Py
thon27', 'C:\\Python27\\lib\\site-packages', 'C:\\Python27\\lib\\site-packages\\
PIL']
>>> eval(exp2)
[1, 4, 9]

5 Comments

My meaning is the reverse of eval. eval input is a string and the output is an expression. I want a input an expression and output a string. So in your example [1, 4, 9] returns exp2 Which seems to me impossible. By the way eval is very dangerous to use that is the reason I use functools.partial
That seems impossible. I don't know what the use scenario is. If you are parsing python code, consider using ast.
I looked to ast but could not find a solution. Could you create some code for sys.path using ast module?
I am still not quite sure of your problem. For get_expression_str(expression) to work, 'expression' has to be a expression string, not the execution result, because there could be a million ways to get that result. But if you already have the string, why parse it?
Understandable question, the same as @jonrsharpe asked. Please read my last reply to jonrsharpe.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.