Как преобразовать строку в функцию в Python?

Например, если у меня есть функция с именем добавить как

def add(x,y):
    return x+y

и я хочу, чтобы возможность преобразовать строку или входные данные, чтобы направить на эту функцию, как

w=raw_input('Please input the function you want to use')

или же

w='add'

Есть ли способ использовать w, чтобы обратиться к функции добавить?

13 ответов

Решение

Поскольку вы принимаете пользовательский ввод, самый безопасный способ - точно определить, что является допустимым:

dispatcher={'add':add}
w='add'
try:
    function=dispatcher[w]
except KeyError:
    raise ValueError('invalid input')

Если вы хотите оценить строки как 'add(3,4)' Вы можете использовать безопасный Eval:

eval('add(3,4)',{'__builtins__':None},dispatcher)

eval в целом может быть опасно при применении к пользовательскому вводу. Выше, безопаснее, так как __builtins__ отключен и locals ограничено dispatcher , Кто-то умнее, чем я мог бы по-прежнему вызывать проблемы, но я не мог сказать вам, как это сделать.

ВНИМАНИЕ: даже eval(..., {'__builtins__':None}, dispatcher) небезопасно для применения к пользовательскому вводу. Злоумышленник может запустить произвольные функции на вашем компьютере, если ему будет предоставлена ​​возможность оценить его строку eval,

Одним из безопасных способов является сопоставление имен и функций. Это безопаснее, чем использовать eval,

function_mappings = {
        'add': add,
}

def select_function():
    while True:
        try:
            return function_mappings[raw_input('Please input the function you want to use')]
        except KeyError:
            print 'Invalid function, try again.'

Решение unutbu - это то, что я обычно использую, но для полноты картины:

Если вы указываете точное название функции, вы можете использовать eval, хотя это очень обескураживает, потому что люди могут делать злые дела:

eval("add")(x,y)

Встроенная функцияeval будет делать то, что вы хотите. Применяются все обычные предупреждения о выполнении произвольного предоставленного пользователем кода.

Если существует ограниченное количество предопределенных функций, вам следует избегать eval и использовать таблицу поиска вместо (т.е. Dict). Никогда не доверяйте своим пользователям.

Просто используйте ссылку на функцию:

def pwr(x, y):
    return x ** y

def add(x, y):
    return x + y

dispatcher = { 'pwr' : pwr, 'add' : add}

def call_func(x, y, func):
    try:
        return dispatcher[func](x, y)
    except:
        return "Invalid function"

call_func(2, 3, 'add')

Просто и безопасно.

Если вы реализуете приложение, похожее на оболочку, в котором пользователь вводит какую-то команду (например, добавить), а приложение отвечает (возвращает сумму), вы можете использовать cmd модуль, который обрабатывает все командные взаимодействия и диспетчеризацию для вас. Вот пример:

#!/usr/bin/env python

import cmd
import shlex
import sys

class MyCmd(cmd.Cmd):
    def do_add(self, arguments):
        '''add - Adds two numbers the print the sum'''
        x, y = shlex.split(arguments)
        x, y = int(x), int(y)
        print x + y

    def do_quit(self, s):
        '''quit - quit the program'''
        sys.exit(0)

if __name__ == '__main__':
    cmd = MyCmd()
    cmd.cmdloop('type help for a list of valid commands')

Вот пример текущей сессии:

$ python cmd_tryout.py
введите help для получения списка допустимых команд
(Cmd) помогите добавить
add - добавляет два числа к распечатке суммы
(Cmd) добавить 5 3
8
(Cmd) выйти

По приглашению (Cmd) вы можете выдать help Команда, которую вы получаете бесплатно. Другие команды add а также quit которые соответствуют do_add() а также do_quit() функции.

Обратите внимание, что команда help отображает строку документации для вашей функции. Строка документа представляет собой строку, следующую непосредственно за объявлением функции (см. do_add() например).

cmd Модуль не разбирает аргументы, не разбирает их, поэтому вы должны сделать это самостоятельно. do_add() Функция иллюстрирует это.

Этого примера программы должно быть достаточно, чтобы вы начали. Для получения дополнительной информации просмотрите страницу справки cmd. Это пустяки, чтобы настроить подсказку и другие аспекты вашей программы.

      def add(x,y):
  print(x+y)

def subtract(x,y):
  print(x-y)

function_list = {'add', 'subtract'}

def caller(func, x, y):
  
  eval(func)(x,y) # more security exploits
  
  if func in function_list:
    eval(func)(x,y) # less security exploits

caller("add", 1, 2)

У меня такая же проблема.

Я рекомендую вам справиться с этим путем создания временного файла Python для хранения функции, введенной пользователем. Вот пример, который я использовал в программе, которую я написал для рисования представлений математических функций:

with open("function.py",'w') as file:
    f=input('enter the function you want to draw example: 2*x+1 or e**x :\n')
    file.write("from math import *\ndef f(x):\n\treturn "+f)

Это создаст файл, содержащий функцию, которую я хочу вызвать.

Затем вы должны вызвать функцию, которую вы написали в файле, в своей программе:

from function import f

Теперь вы можете использовать свою функцию как обычную функцию Python.

Если вы хотите, вы также можете удалить файл, в котором вы сохранили свою функцию, с помощью os.remove:

import os
os.remove("function.py")

Чтобы помочь вам понять, вот моя программа для рисования математических функций:

import numpy
import cv2
import os
from math import *


def generate(f,a,b,min,max,functionname='noname'):
    ph=(b-a)/1920
    pv=(max-min)/1080
    picture=numpy.zeros((1080,1920))
    for i in range(0,1920):
        picture[1079-(int((f(a+(i+1)*ph)*1080/max))),i]=255
    for i in range(1920):
        picture[1079-(int((f(a+(i+1)*ph)*1080/max)))+1,i]=255
    cv2.imwrite(functionname+'.png',picture)


with open("function.py",'w') as file:
    f=input('enter the function you want to draw example: or e**x :\n')
    file.write("from math import *\ndef f(x):\n\treturn "+f)

from function import f
os.remove("function.py")
d=input('enter the interval ,min ,max and the image file name. Separate characters with spacebar. Example: 0 1 0 14 exponontielle :\n').split(" ")
generate(f,int(d[0]),int(d[1]),int(d[2]),int(d[3]),d[4])

У меня было много ситуаций, когда мне нужно было сравнивать строку с int и наоборот в шаблоне Django.

Я создал фильтр, который позволил мне передать имя функции и с помощью eval() преобразовать его.

Пример:

Шаблон:

{% ifequal string int|convert:'str' %} do something {% endifequal %}

Фильтр шаблона (где я использую строку для вызова имени функции):

@register.filter
def convert(value, funcname):
    try:
        converted = eval(funcname)(value)
        return converted
    except:
        return value

Что касается вопроса Джона Карри выше ... если вам нужна версия кода Джефферсона Феликса, которая обрабатывает несколько аргументов, то самое простое решение - предоставить аргументы в списке и организовать для каждой отправленной функции проверку количества аргументов. прежде чем продолжить.

Вот простая версия, которую я только что протестировал в коде Visual Studio:

      import math

def sin(args):
    argc = len(args)
    if (argc == 1):
        result = math.sin(args[0])
    else:
        result = None

    return(result)

def sum(args):
    argc = len(args)
    if (argc == 2):
        result = args[0] + args[1]
    else:
        result = None

    return(result)


def dot_product(args):
    argc = len(args)
    if (argc == 2):
        vector1 = args[0]
        vector2 = args[1]
        if (len(vector1) == 3 and len(vector2) == 3):
            result = (vector1[0] * vector2[0]) + (vector1[1] * vector2[1]) + (vector1[2] * vector2[2])
        else:
            result = None
    else:
        result = None

    return(result)

dispatcher = {"sin" : sin, "sum" : sum, "dot_product" : dot_product}

def call_func(dispatcher, func_name, args):
    func_list = list(dispatcher.keys())
    if (func_list.count(func_name) == 0):
        return(None)
    else:
        return(dispatcher[func_name](args))

val = call_func(dispatcher, "sin", [0.6])
print(f"Sine is : {val}")

val = call_func(dispatcher, "sum", [4, 6])
print(f"sum is : {val}")

val = call_func(dispatcher, "dot_product", [[3, 7, 2], [5, 9, 4]])
print(f"dot product is : {val}")

Результат выглядит следующим образом:

      Sine is : 0.5646424733950354
sum is : 10
dot product is : 86

Конечно, более сложная версия будет включать улучшенный перехват ошибок, который просто возвращает «None», если обнаружена ошибка, но вышеизложенное можно использовать в качестве шаблона для дальнейшего развития. Точно так же функция dot_product может быть улучшена для обработки векторов любой размерности, но я оставляю это в качестве упражнения для читателя ...

Этот вопрос, очевидно, относится к загадочной эпохе Python, но я подумал, что это может быть полезно для людей, попавших сюда из поисковых систем.

В настоящее время вы можете сделать что-то вроде этого (ручное сопоставление не требуется):

      func = locals()[ name ]
print( f'Found local function {func}' )
func()

Конечно, для любого реального варианта использования вы должны убедиться, что вы действительно что-то нашли, прежде чем выполнять это!

Чтобы обобщить и улучшить эти ответы :

      import operator


def foo():
    return "foo called"


def bar(x, y):
    return "bar called with args", x, y


def baz(x, kwarg=42):
    return "baz called with args", x, kwarg


callables = {
    "foo": foo,
    "bar": bar,
    "baz": baz,
    "add": operator.add,
}
def call(func_name, *args, **kwargs):
    return callables[func_name](*args, **kwargs)


if __name__ == "__main__":
    print(call("foo"))  # => foo called
    print(*call("bar", 1, 2))  # => bar called with args 1 2
    print(*call("baz", 3, kwarg=42))  # => baz called with args 3 42
    print(call("add", 4, 5))  # => 9

В общем, используйте*args, **kwargsидиома для любой ситуации, когда вы хотите передать произвольные аргументы обернутой функции.

Лучше поднять, если функция отсутствует, чем возвращать случайную магическую строку, желательно с пользовательской ошибкой, напримерFunctionNotCallableпоэтому его можно отличить от возвращаемых значений и ошибок функций, которые вы вызываете.

Для варианта использования OP, как вы можете видеть,operatorв библиотеке уже есть функциональные версии операторов.

[Я попал сюда через дублирующий вопрос. Моей первой мыслью было использовать argparse а также shlex и я не видел этого здесь, поэтому я добавляю это как еще один вариант.]

Вы могли бы использовать argparse настроить реестр функций / команд и безопасно разобрать их аргументы. Это также обеспечит некоторый уровень удобства для пользователя, например, давая вам знать, когда вы ввели команду, которая не существует.

import argparse
import shlex

def hello(name):
    print('hello,', name)

def main():
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers()

    hello_parser = subparsers.add_parser('hello')
    hello_parser.add_argument('name')
    hello_parser.set_defaults(func=hello)

    print('Enter q to quit')

    while True:
        command = input('command> ')
        command = command.strip()

        if not command:
            continue

        if command.lower() == 'q':
            break

        words = shlex.split(command)

        try:
            args = parser.parse_args(words)
        except SystemExit:
            # argparse will sys.exit() on -h and errors; prevent that
            continue

        func_args = {name: value for name, value in vars(args).items()}
        del func_args['func']
        args.func(**func_args)

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print()
Другие вопросы по тегам