#python
#python
Вопрос:
Я хотел бы вызвать функцию из пользовательского ввода, но включить аргументы в круглые скобки. Например, если у меня есть функция, которая принимает один аргумент:
def var(value):
print(value)
Я хотел бы запросить у пользователя команду и аргументы, а затем вызвать функцию с аргументами:
Input Command: var("Test")
Test
Комментарии:
1. @MasterHolbytla ваши входные данные всегда будут такими же простыми, как
var(arg1, arg2, arg3)
? Или это становится еще сложнее?2. И под «любым» вводом мы подразумеваем любой ввод. Вредоносные тоже. Не делайте этого. Использовать. Оценка.
Ответ №1:
Отделите имя функции от аргументов. Найдите функцию по имени, используя предопределенную карту. Проанализируйте аргументы с помощью literal_eval
. Вызовите функцию с аргументами.
available = {}
def register_func(f):
available[f.__name__] = f
@register_func
def var(value):
print(value)
from ast import literal_eval
def do_user_func(user_input):
name, args = user_input.split('(', 1)
return available[name](*literal_eval('(' args[:-1] ',)'))
do_user_func("var('test')") # prints "test"
Это все еще невероятно хрупко, любой неверный ввод приведет к сбою (например, забывание скобок или неверное имя функции). Вам решать, как сделать это более надежным.
literal_eval
все еще несколько небезопасно при ненадежном вводе, поскольку можно создавать небольшие строки, которые вычисляются для больших объемов памяти. '[' * 10 ']' * 10
, для безопасного, но наглядного примера.
Наконец, не используйте eval
при ненадежном пользовательском вводе. Нет практического способа защитить ее от вредоносного ввода. Хотя она будет оценивать ожидаемый вами ввод, она также будет оценивать код, который, например, удалит все ваши файлы.
Любая попытка сделать eval
безопасным в конечном итоге окажется более сложной, чем любое из приведенных здесь решений, без какой-либо практической пользы. Это все равно будет небезопасно каким-то образом, которого вы не ожидали. Не делайте этого.
Ответ №2:
Я собираюсь опубликовать это решение в качестве альтернативы, исходя из предположения, что вы имеете дело с простыми входными данными, такими как:
var(arg)
Или один вызов функции, который может принимать список позиционных аргументов.
Использование eval
было бы ужасной, не рекомендованной идеей, как уже упоминалось. Я думаю, что это угроза безопасности, о которой вы читали.
Идеальный способ выполнить этот подход — иметь словарь, сопоставляющий строку с методом, который вы хотите выполнить.
Кроме того, вы можете рассмотреть альтернативный способ сделать это. Вводите данные через пробел, чтобы знать, как вызывать вашу функцию с аргументами. Рассмотрим ввод, подобный этому:
"var arg1 arg2"
Поэтому, когда вы вводите это:
call = input().split()
Теперь у вас будет:
['var', 'arg1', 'arg2']
Теперь вы можете считать свой первый аргумент функцией, а все остальное — аргументами, которые вы передаете функции. Итак, в качестве функционального примера:
def var(some_arg, other_arg):
print(some_arg)
print(other_arg)
d = {"var": var}
call = input().split()
d[call[0]](*call[1:])
ДЕМОНСТРАЦИЯ:
var foo bar
foo
bar
Ответ №3:
Вам следует изучить модуль cmd. Это позволяет вам анализировать ввод, аналогичный командам оболочки, но я полагаю, что вы можете усложнить и изменить разделители, если скобки являются важной частью спецификации.
Ответ №4:
Вместо использования eval вы можете проанализировать его самостоятельно. Таким образом, у вас есть контроль над тем, как каждая функция должна анализировать / десериализовать аргументы пользовательского ввода.
import sys, re
def custom_print(value):
print value
def custom_add(addends):
print sum(addends)
def deserialize_print(args):
# just print it as is
custom_print(args)
def deserialize_add(args):
# remove all whitespace, split on commas, parse as floats
addends = [float(x) for x in re.sub(r"s", "", args).split(",")]
# send to custom_add function
custom_add(addends)
def get_command():
cmd_input = raw_input("Command: ")
# -- check that the command is formatted properly
# and capture command groups
match = re.match(r"^([a-zA-Z0-9] )((.*))?$", cmd_input)
if match:
# extract matched groups to separate variables
(cmd, argstring) = match.groups()
# strip parenthesis off of argstring
if argstring:
args = argstring[1:-1]
# send the whole argument string to its corresponding function
if cmd == "print":
deserialize_print(args)
elif cmd == "add":
deserialize_add(args)
elif cmd == "exit":
sys.exit()
else:
print "Command doesn't exist."
else:
print "Invalid command."
# recurse until exit
get_command()
# -- begin fetching commands
get_command()
Это довольно грубая настройка, хотя вы можете обойтись дополнительной проверкой ошибок и улучшением функций десериализации и модульных дополнений функций.
Если несвязанных функций десериализации кажется слишком много, вы также можете просто перенести десериализацию в сами пользовательские функции.
Ответ №5:
Ниже приведен пример функции, вызываемой из пользовательского ввода, с использованием класса:
class Wash:
def __init__(self, amount):
self.amount = amount
if amount == 12:
print("Platinum Wash")
elif amount == 6:
print("Basic Wash")
else:
print("Sorry!")
amount = int(input("Enter amount: "))
payment = Wash(amount)