Python:运行时动态创建函数

80

如何在Python中动态创建函数?

我在这里看到了一些答案,但是没有找到一个可以描述最常见情况的答案。

考虑以下示例:

def a(x):
    return x + 1
如何动态创建这样的函数?我需要使用compile('...', 'name', 'exec')吗?但是然后呢?创建一个虚拟函数并将其代码对象替换为编译步骤中的代码对象吗?
或者应该使用types.FunctionType?怎么用?
我想自定义所有内容:参数数量、它们的内容、函数体中的代码、结果等等。

5
可能是重复的问题:Python中是否可以实现真正的动态和匿名函数?(原链接:https://dev59.com/_Wkv5IYBdhLWcg3w3Eal) - Martijn Pieters
我不认为这是重复的:dynf = FunctionType(compile('def f(x): return x + 3', 'dyn.py', 'exec'), globals())print dynf(1)会出现TypeError: '<module>() takes no arguments (1 given)'错误。 - Ecir Hana
2
仅仅因为那里的答案可能是错误的,并不意味着这不是一个重复的问题。 - Martijn Pieters
该链接的问题有一个更新的答案,演示了如何创建带参数的函数。 - Martijn Pieters
你确定你不能用 Python 中已有的函数式编程(lambda、闭包、组合等)来实现你想要的东西吗?代码对象很棘手,而且它们没有得到很好的文档支持(或者根本就没有)。此外,它们被视为内部实现,可能会在有或没有通知的情况下进行更改。 - Xingzhou Liu
9个回答

44

使用 exec

>>> exec("""def a(x):
...   return x+1""")
>>> a(2)
3

2
如果出现 NameError 错误,请考虑使用 locals().get('a') - ניר

39

你看到了 这个 吗?它是一个例子,告诉你如何使用 types.FunctionType

示例:

import types

def create_function(name, args):
    def y(): pass

    y_code = types.CodeType(args,
                            y.func_code.co_nlocals,
                            y.func_code.co_stacksize,
                            y.func_code.co_flags,
                            y.func_code.co_code,
                            y.func_code.co_consts,
                            y.func_code.co_names,
                            y.func_code.co_varnames,
                            y.func_code.co_filename,
                            name,
                            y.func_code.co_firstlineno,
                            y.func_code.co_lnotab)

    return types.FunctionType(y_code, y.func_globals, name)

myfunc = create_function('myfunc', 3)

print repr(myfunc)
print myfunc.func_name
print myfunc.func_code.co_argcount

myfunc(1,2,3,4)
# TypeError: myfunc() takes exactly 3 arguments (4 given)

4
是的,我看到了,它似乎正是我想要的。但是我不知道如何创建/修改函数体和返回值..? - Ecir Hana
2
我刚试了一下,当你用正确的参数数量更改结尾处的调用时,它会导致段错误... - Julien Greard
1
你有没有尝试过执行这段代码?因为这段代码无法运行。 - Module_art
是的,在Python 3中不存在 func_Code 属性。 - Melendowski
原始帖子说:“你看到这个了吗?”然而,原始网页现在已经失效了。http://snipplr.com/view/17819/不存在。 - Samuel Muldoon
链接仅供参考,但您可以从上面粘贴的示例中了解到相关内容。 - Ahsan

32

如果您需要从特定模板动态创建函数,请尝试使用以下代码:

def create_a_function(*args, **kwargs):

    def function_template(*args, **kwargs):
        pass

    return function_template

my_new_function = create_a_function()

在函数create_a_function()内,您可以控制选择哪个模板。内部函数function_template作为模板。创建函数的返回值是一个函数。分配之后,您将使用my_new_function作为常规函数。

通常,这种模式用于函数修饰符,但在这里也可能很方便。


谢谢!正是我想要的。我的用例是基于我动态生成的类属性创建一个函数。但它不应该依赖于“self”。因此,它是一个静态函数 create_a_function,参数作为参数。这样,属性可以在内部函数中作为普通变量使用,并且函数是分离的 :-) - Nils
2
它有一些限制,无法通过pickle进行序列化。 - Fu Hanxi

14
您可以使用lambda来完成这个任务。
a = lambda x: x + 1
>>> a(2)
3

2
这几乎就是我现在正在使用的。在我的情况下,我需要动态构建一个函数作为字符串或预编译一个配置的字符串表达式,所以我使用了 a = eval("lambda x: x + 1"),但结果是相同的。在我的情况下,我还为表达式提供数学函数,因此我还在评估和执行 lambda 的 Python 脚本中添加了 from math import * - Chris Tophski
请注意,您可以使用此功能创建函数列表或字典,以便像generic['d'](3)一样运行。 - endolith

9

这种方法怎么样?

在这个例子中,我将一元一次函数(x -> ax+b)的一阶函数参数化到一个类中:

class Fun: 
  def __init__(self, a,b):
    self.a, self.b = a,b

  def f(self, x):
    return (x*self.a + self.b)

 u = Fun(2,3).f

这里的 u 将是函数 x->2x+3。


总是可以将functools.partial应用于lambda表达式:u = partial(lambda a, b, x: x*a + b, 2, 3) - cowbert

4
您可以按照以下方式进行操作:
new_func='def next_element(x):\n  return x+1'
the_code=compile(new_func,'test','exec')
exec(the_code)
next_element(1)

这与以前的exec解决方案类似。


1
比Berci的回答更简单易懂。
def get_fn(a, b): # factory function
  def fn(): # result function
    print(a, b)
  return fn

fn = get_fn(1, 2)

fn()

这对于将变量转换为常量(“动态函数的模板变量”)非常有用。


0
在我的情况下,我需要一个返回函数以将其保存到变量中的函数:
def create_function(function_name, function_arguments, function_code):
    function_string = f"def {function_name}({function_arguments}): {function_code}"
    exec(function_string)
    return_value = eval(function_name)
    return return_value

name = 'say_hello'
arguments = 'name'
code = 'print(f"Hello, {name}!")'

myfunc = create_function(name, arguments, code)

print(myfunc, type(myfunc))
myfunc('John Doe')

0
为了完整起见,动态创建函数的工作方式可以简单地进行参数化。
def dynamically_create_greeting(greet_style):
    def inner(name):
        print(f'{greet_style} {name}')

    return inner


say_hello = dynamically_create_greeting(greet_style='hello')
say_hello('John')

say_hi = dynamically_create_greeting(greet_style='hi')
say_hi('John')

这将产生输出 >

你好 John

嗨 John


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接