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, вы можете использовать его как угодно / когда угодно. Также обратите внимание, что с моим решением вам даже не нужно знать имя функции, определенной пользователем. Если у вас есть объект функции, имя не имеет значения.

Другие вопросы по тегам