31

就像可以使用 type(name, base-classes, namespace-dict) 创建动态类一样,是否可以创建动态函数?

我尝试按照以下方式做一些事情:

>>> f = type("f", (function,), {})
NameError: name 'function' is not defined

好的,所以我会很聪明,但是:

>>> def fn():
...   pass
... 
>>> type(fn)
<type 'function'>
>>> f = type("f", (type(fn),), {})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type 'function' is not an acceptable base type

Python 是否像允许动态类一样专门阻止创建动态函数?

编辑:注意,我不允许任何使用 exec.. 因为我的问题是 Python 语言本身是否允许这样做。

提前致谢。

4

8 回答 8

47

您可以使用它types.FunctionType来动态创建函数,例如

def test_func(): print 'wow' 
dynf = types.FunctionType(test_func.func_code, {})
dynf()

输出:

wow

您可能会反对这不是动态的,因为我正在使用来自另一个函数的代码,但这只是一个示例,有一种方法可以从 python 字符串生成代码,例如

dynf = types.FunctionType(compile('print "really WoW"', 'dyn.py', 'exec'), {})
dynf()

输出:

really WoW

现在是动态的!

OP 担心这种功能的动态性质,所以这里是另一个例子

dynf = types.FunctionType(compile('test_func():\ntest_func()', 'dyn.py', 'exec'), globals())
dynf()

输出:

wow
wow

注意:像这样创建 Function 对象似乎有局限性,例如传递参数并不容易,因为要传递参数,我们需要将正确的 co_argcount、co_varnames 和其他 12 个变量传递给types.CodeType,理论上可以做到但容易出错,更简单的方法是将字符串作为模块导入,并且您拥有完整的功能,例如

import types
import sys,imp

code = """def f(a,b,c):
    print a+b+c, "really WoW"
"""
module = imp.new_module('myfunctions')
exec code in module.__dict__
module.f('W', 'o', 'W')

输出:

WoW really WoW
于 2012-04-24T18:07:20.787 回答
14

您将需要查看collections.Callable,这只是定义__call__.

from collections import Callable
class SomeCallableClass(Callable):
    def __call__(self, x):
        print(x)

some_function = SomeCallableClass()
some_function(1)

将给我们1作为输出。这使您可以随意构造函数。

from collections import Callable
class SomeCallableClass(Callable):
    def __init__(self, n):
        self.n = n
    def __call__(self, x):
        for i in range(self.n):
            print(x)

some_function = SomeCallableClass(2)
some_function("Two times.")
some_function = SomeCallableClass(3)
some_function("Three times.")

这给了我们:

Two times.
Two times.
Three times.
Three times.
Three times.

您可以使用它来构建您想要的复杂功能。

于 2012-04-24T18:07:58.147 回答
12

exec如果您准备生成抽象语法树 (AST)并编译它们,则可以避免生成 then源代码。它可能会稍微好一些,因为数据可以一直保持结构化。

from ast import *
from types import *

function_ast = FunctionDef(
    name='f',
    args=arguments(args=[], vararg=None, kwarg=None, defaults=[]),
    body=[Return(value=Num(n=42, lineno=1, col_offset=0), lineno=1, col_offset=0)],
    decorator_list=[],
    lineno=1,
    col_offset=0
)
module_ast = Module(body=[function_ast])

module_code = compile(module_ast, "<not_a_file>", "exec")
function_code = [c for c in module_code.co_consts if isinstance(c, CodeType)][0]

f = FunctionType(function_code, {})

print f()

上面的代码将打印42.

要获得有关生成的 AST 应该是什么的灵感,您可以使用:

print(dump(parse("def f(): return 42"), include_attributes=True))

当然,AST 在 Python 2 和 Python 3 中是不同的。

编辑:

在 Python 3.8 中测试和工作

from ast import *
from types import *

function_ast = FunctionDef(
    name='f',
    args=arguments(
        args=[], vararg=None, kwarg=None, defaults=[],
        kwonlyargs=[], kw_defaults=[], posonlyargs=[]
    ),
    body=[Return(value=Num(n=42, lineno=1, col_offset=0), lineno=1, col_offset=0)],
    decorator_list=[],
    lineno=1,
    col_offset=0
)
module_ast = Module(body=[function_ast], type_ignores=[])

module_code = compile(module_ast, "<not_a_file>", "exec")
function_code = [c for c in module_code.co_consts if isinstance(c, CodeType)][0]

f = FunctionType(function_code, {})

print(f())
于 2015-04-28T18:45:36.073 回答
4

很多人似乎被 Python 中“lambda”的用途误导了:它的唯一目的是定义一个没有名称的简单单表达式函数。而已。在 Python 中,函数确实是一等对象,就像它们在 LISP 中一样:您可以将它们作为参数传递,将它们存储在数据结构中,然后将它们作为结果返回。例如,这是一个组合两个给定函数 f 和 g 的函数,因此 compose(f, g)(x) 等价于 f(g(x)):

def compose(f, g) :
    def result(x) :
        return f(g(x))
    #end result
    return result
#end compose

这是一个使用示例:

>>> h = compose(lambda x : x + 2, lambda x : x * x)
>>> h(3)
11
于 2012-04-25T02:13:52.797 回答
4

是的。使用def语句:

def functor(f): # this dynamically creates a function at module load time
    def inner(g): #this dynamically creates a function on execution of functor
        return f(g)

    return inner

任何涉及正在编译的独立文本的解决方案都等效于execor eval,这使您可以使用预先存在的函数和词法捕获的数据项来将新函数拼接在一起。可能会给你一些想法。

于 2013-05-30T19:26:36.847 回答
2

Python 确实允许创建动态函数。一种方法是使用 lambda:

>>> g = lambda x: x**2
>>> g
<function <lambda> at 0xa68c924>
>>> g(3)
9
>>> g = lambda x: x*2
>>> g
<function <lambda> at 0xa68c95c>
>>> g(3)
6
>>> 

此处描述了另一种方法:Python 中的词法闭包

因此,您不需要像Strategy这样的行为模式的骗局。

如果您能告诉我们您想要解决的问题,这将很有用,这样我们就可以找出适合该问题的语言结构。

于 2012-04-24T18:16:33.133 回答
0

jacquev6 的解决方案在为 Python 3 更新后对我来说效果很好,方法是将kwonlyargs=[], kw_defaults=[]添加到 arguments() 的调用中:

#!/usr/bin/env python3

from ast import *
from types import *

function_ast = FunctionDef(
    name='f',
    args=arguments(args=[], vararg=None, kwarg=None, defaults=[], kwonlyargs=[], kw_defaults=[]),
    body=[Return(value=Num(n=42, lineno=1, col_offset=0), lineno=1, col_offset=0)],
    decorator_list=[],
    lineno=1,
    col_offset=0
)
module_ast = Module(body=[function_ast])
module_code = compile(module_ast, "<not_a_file>", "exec")
function_code = [c for c in module_code.co_consts if isinstance(c, CodeType)][0]

f = FunctionType(function_code, {})

print(f())
于 2019-03-09T19:19:44.267 回答
0

python 对匿名可执行对象提供有限的支持,那就是 lambda,虽然它不支持语句,只支持表达式。

这是由于 python 的语法,不会有简单的解决方法。

如果您需要生成更复杂的可执行对象,那么不要匿名,只需在本地范围内创建一个名称不冲突的函数对象即可。

这些在文档中进行了描述。

Python lambda 表达式不能包含语句,因为 Python 的语法框架无法处理嵌套在表达式中的语句。但是,在 Python 中,这不是一个严重的问题。与其他语言中添加功能的 lambda 形式不同,如果您懒得定义函数,Python lambda 只是一种简写符号。

函数已经是 Python 中的第一类对象,并且可以在本地范围内声明。因此,使用 lambda 而不是本地定义的函数的唯一优点是您不需要为函数发明名称——但这只是函数对象的局部变量(与对象类型完全相同)一个 lambda 表达式产生)被分配!

https://docs.python.org/3.7/faq/design.html#why-can-t-lambda-expressions-contain-statements

于 2021-11-30T23:16:55.717 回答