3

I want to pass function definition to a python command line script. What is the best way to do this? I am using python 2. Suppose i have a script like this:

#myscript.py
x = load_some_data()
my_function = load_function_definition_from_command_line()
print my_function(x)

And i want to call it like this: python myscript.py 'def fun(x): return len(x)'

How do i perform the load_function_definition_from_command_line part ?

I imagine a workaround:

  1. get the string function definition from command line
  2. write it to a file with .py extension in some temp directory
  3. load the definition from file using solutions from this question: How to import a module given the full path?
  4. execute
  5. cleanup

But I am sure there must be a better way.

Community
  • 1
  • 1
Jan Grz
  • 1,373
  • 14
  • 18
  • 1
    I think you should explain the reason why you want to pass in a function as a string to get ran. This may help us better help you, and possibly find a workaround to having to use what the current answers are suggesting (eval and exec). Evaluating a command line argument and running it as code is EXTREMELY dangerous, and shouldn't ever be done unless ABSOLUTELY necessary. – disflux Jan 15 '16 at 14:08
  • Just out of curiosity - why do you need this? I'm asking because it leads to very bad solution (eval) and perhaps you just don't see the other possibilities.. – Jan Spurny Jan 15 '16 at 14:09
  • I am writing a shell application for map/reduce/filter operations on logs/outputs from other applications – Jan Grz Feb 11 '16 at 13:53

4 Answers4

2

You can use eval to run code defined in a string. Like so:

import sys

x = load_some_data()
function = eval("".join(sys.argv[1:]))
print(function(x))

With your specific example though you might have to use something like lambda x: len(x)

As @Jan-Spurny rightly points out: "Never, never, never use eval unless you're absolutely sure there is no other way. And even then you should stop and think again."

In my mind the better strategy would be to turn the data loader and executor into a module with a method that takes a function as an argument and runs the desired code. The end result something like this:

import data_loader_and_executor

def function(x):
    return len(x)

data_loader_and_executor.run(function)
asadmshah
  • 1,338
  • 9
  • 7
  • 1
    I would strongly suggest to make the last sentence (''Ideally, one shouldn't ever have to use eval.'') more visible (bold) or rephrase it, for example as: "Never, never, never use `eval` unless you're **absolutely** sure there is no other way. And even then you should stop and think again." – Jan Spurny Jan 15 '16 at 14:06
  • What is the danger behind eval? – Jan Grz Jan 15 '16 at 14:20
  • 2
    @JanOsch - someone could pass literally **anything** and it will be executed. For example some joker can pass: "def f(x): import shutil; shutil.rmtree('/');" and delete your filesystem. Eval is the biggest most dangerous backdoor to your program. – Jan Spurny Jan 15 '16 at 14:26
0

Use function exec:

import sys

def load_function_definition_from_command_line():
   exec(sys.argv[1])
   return locals()['fun']

Of course you have to know, how your function will be named, but this can be done by passing to your argument second argument:

 $ python myscript.py 'def fun(x): return len(x)' fun

And then your function will look like:

import sys

def load_function_definition_from_command_line():
   exec(sys.argv[1])
   return locals()[sys.argv[2]]

!!Remember though, that evaluating user input is very dangerous!!

Edit: Since fun would be the only object defined in locals, you can just return first element in locals():

def load_function_definition_from_command_line():
   exec(sys.argv[1])
   return locals()[0]
kendriu
  • 565
  • 3
  • 21
0

You can use eval or exec to create a function in your current namespace.

exec "somefunc(x): return x * 2"
somefunc(2) # 2

Example within your context

python load_function.py "def f(x): return x * 2"

//load_function.py
import sys

exec sys.argv[1]
print f(2)

Command line output:
4

Edit: Obligatory, "It is not wise to execute user input like this."

trendsetter37
  • 582
  • 8
  • 15
0

The most obvious source for the correct answer on how to do this is in the timeit python builtin library.

It is invoked like this:

$ python -m timeit '"-".join(str(n) for n in range(100))'

and you can find the source code here, which uses compile and exec to invoke the code from the command line

https://hg.python.org/cpython/file/2.7/Lib/timeit.py#l143

rbp
  • 1,850
  • 15
  • 28