Python: есть пользовательская функция в качестве входных данных при сохранении недоступности исходного кода?
Мне нужно написать часть программного обеспечения, которая принимает пользовательскую функцию (написанную на python) в качестве входных данных.
Пользовательская функция берет вектор чисел и возвращает вектор чисел. Мое программное обеспечение будет вызывать эту пользовательскую функцию много раз (так же, как это делал бы поиск в корне), а затем возвращает некоторый результат.
Исходный код моего софта будет написан на python (он будет использовать *.pyd) или на C++ и должен быть скрыт от пользователя.
Каков наилучший способ (если есть...) для достижения этого? В идеале мой код Python должен быть скомпилирован в *.exe, а пользователь должен скопировать и вставить свою функцию в текстовое поле, но использование этого из интерпретатора Python также должно быть приемлемым.
1 ответ
Вот очень ограниченный пример, который показывает, как вы могли бы это сделать - Конечно, здесь есть некоторые ограничения - В основном, это работает, только если пользователь вводит только одну функцию. Если строка, которую они пишут, выглядит примерно так:
a='garbage'
def foo():pass
или даже:
def bar():
return foobar()
def foobar():
return "foobar is a cool word, don't you think?"
тогда тебе не повезло. (Другими словами, это предполагает, что пользователь добавляет только одну вещь в пространство имен функции run_user). Конечно, вы можете проверить это и вызвать исключение или что-то еще, если окажется, что пользователь добавил слишком много... Вы также можете вернуть функцию и использовать ее, как предложено gauden.
def run_user(S):
#S is the user's function as a string.
lvars=None #make sure the name is in locals()
lvars=set(locals())
exec(S) #exec isn't usually a good idea -- but I guess you're a very trusting person.
usr_namespace=list(set(locals())-lvars)
usr_func_name=usr_namespace[0]
if(len(usr_namespace)>1):
raise ValueError("User input too much into the namespace!")
usr_func=locals()[usr_func_name]
usr_func() #comment this out if you don't want to run the function immediately
return usr_func
usr_string="""
def foo():
a="Blah"
print "Hello World! "+a
"""
func_handle=run_user(usr_string) #prints "Hello World! Blah"
#and to demonstrate that we can pass a handle to the function around:...
func_handle() #prints "Hello World! Blah" again.
Обратите внимание, что вы можете сделать это немного более безопасно, используя Python 3 exec
или питон 2 execfile
где вы можете ограничить пространство имен функции пользователя, передав словарь {'__builtins__':None}
как глобальный словарь
#python3.x
allowed=vars(__builtins__).copy()
allowed['__import__']=None
exec("import os",{'__builtins__':None},allowed) #raises ImportError
exec("print(abs(-4))",{'__builtins__':None},allowed) #prints 4 as you'd expect.
Я ожидаю, что то же самое будет работать с execfile
под python2.x, если вы записали строку во временный файл...
РЕДАКТИРОВАТЬ (для устранения комментариев ниже)
Пример, который вы предоставляете eval
можно сделать немного проще:
a=5
b=eval('a+5') #b == 10
Тем не менее, это не то, что вы просили. Вы просили, чтобы пользователь мог написать функцию, например:
def f(a):
return a+5
Первый случай будет работать, но пользователь должен знать, что имя переменной - "a".
a=5
b=eval('x+5') #won't work -- x isn't defined
Им также нужно знать, как добавить векторы - (Если вы используете пустые массивы, это тривиально, но я подумал, что упомяну об этом на всякий случай, если вы этого не сделаете). И они не могут создавать сложные выражения (длинные выражения с использованием нескольких условных выражений, циклов и т. Д.) Без приличной работы и скрежетов.
Последний случай немного лучше (на мой взгляд), потому что он гораздо более общий. Вы можете получить функцию, используя метод, который я описал (удаляя часть, где я на самом деле запускаю функцию), и пользователь может использовать любые имена переменных, которые он хочет - тогда вы просто используете их функцию. Они также могут делать такие вещи, как циклы, и использовать выражения, которые намного сложнее, чем вы могли бы сделать в одной строке с eval
, Единственное, за что вы платите, это то, что пользователю нужно написать def func(...):
а также return some_value
в конце этого, который, если они знают, Python должен быть полностью интуитивным.
ss="""
def foo(x):
return 5+x
"""
a=5
func=run_user(ss)
result=func(a) #result = 10
Это также имеет то преимущество, что строку не нужно пересматривать каждый раз, когда вы хотите вызвать функцию. Когда у вас есть func
, вы можете использовать его как угодно / когда угодно. Также обратите внимание, что с моим решением вам даже не нужно знать имя функции, определенной пользователем. Если у вас есть объект функции, имя не имеет значения.