如何在Python中将字符串转换为函数?

How to convert a string to a function in python?

例如,如果我有一个名为add like的函数

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

我希望能够将字符串或输入转换为直接指向函数

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

1
w='add'

有没有办法用w来引用函数add?


由于您正在接受用户输入,最安全的方法是准确定义什么是有效输入:

1
2
3
4
5
6
dispatcher={'add':add}
w='add'
try:
    function=dispatcher[w]
except KeyError:
    raise ValueError('invalid input')

如果要计算像'add(3,4)'这样的字符串,可以使用安全eval:

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

当应用于用户输入时,eval通常是危险的。由于__builtins__被禁用,而locals被限制为dispatcher,因此上述方法更安全。比我聪明的人可能仍然会制造麻烦,但我不能告诉你如何去做。

警告:即使是eval(..., {'__builtins__':None}, dispatcher)也不安全,不能应用于用户输入。如果有机会让eval评估其字符串,恶意用户可以在您的计算机上运行任意函数。


一种安全的方法是从名称映射到函数。它比使用eval更安全。

1
2
3
4
5
6
7
8
9
10
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,尽管这是非常不鼓励的,因为人们可以做恶意的事情:

1
eval("add")(x,y)

内置的函数eval将满足您的需要。关于执行任意用户提供的代码的所有常见警告都适用。

如果预定义函数的数量有限,则应避免使用eval,而是使用查找表(即Dict)。永远不要相信你的用户。


如果您正在实现一个类似shell的应用程序,其中用户输入一些命令(如add)和应用程序响应(返回sum),那么您可以使用cmd模块,该模块为您处理所有命令交互和调度。下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/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
type help for a list of valid commands
(Cmd) help add
add - Adds two numbers the print the sum
(Cmd) add 5 3
8
(Cmd) quit

在提示(cmd)下,您可以免费发出help命令。其他命令是addquit,它们对应于do_add()do_quit()功能。

注意,HELP命令显示函数的docstring。docstring是紧跟在函数声明之后的字符串(例如,请参见do_add())。

cmd模块不进行任何参数拆分、解析,因此您必须自己进行。do_add()函数说明了这一点。

这个示例程序应该足以让您开始。有关详细信息,请参阅cmd帮助页。定制提示和程序的其他方面是很琐碎的。


只需使用函数引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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')

简单安全。


在很多情况下,我需要在django模板中将字符串与int进行比较,反之亦然。

我创建了一个过滤器,允许我传入函数名并使用eval()转换它。

例子:

模板:

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

模板筛选器(我在其中使用字符串调用函数名):

1
2
3
4
5
6
7
@register.filter
def convert(value, funcname):
    try:
        converted = eval(funcname)(value)
        return converted
    except:
        return value

[我通过一个重复的问题来到这里。我的第一个想法是使用argparseshlex,我在这里没有看到,所以我将它作为另一个选项添加。]

您可以使用argparse来设置函数/命令的注册表并安全地解析它们的参数。这也将提供某种程度的用户友好性,例如,当您输入了一个不存在的命令时通知您。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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()