Python中可以使用真正的动态和匿名函数吗?

时间:2012-04-24 17:48:53

标签: python

正如可以使用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语言本身是否允许这样做。

提前致谢。

7 个答案:

答案 0 :(得分:37)

可以使用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

答案 1 :(得分:12)

您需要查看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.

您可以根据需要使用它来构建复杂的函数。

答案 2 :(得分:8)

如果您已准备好生成Abstract Syntax Trees (AST's)并编译它们,则可以避免生成exec源代码。它可能稍微好一点,因为数据可以一直保持结构化。

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中有所不同。

答案 3 :(得分: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

答案 4 :(得分:2)

是。使用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

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

答案 5 :(得分:1)

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
>>> 

此处描述了另一种方法:Lexical closures in Python

所以,你不需要像Strategy那样的行为模式。

如果你能告诉我们你想要解决的问题,那将是有用的,这样我们就可以找到适合的语言结构。

答案 6 :(得分:0)

jacquev6的解决方案对我来说效果很好在Python 3上对其进行更新之后,方法是在对arguments()的调用中添加 kwonlyargs = [],kw_defaults = [] : / p>

#!/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())