使用自定义名称创建Python动态函数

时间:2012-11-01 19:38:26

标签: python python-2.7 closures metaprogramming dynamic-function

如果已经提出并回答了这个问题,请道歉。 我需要做的是概念非常简单,但不幸的是我无法在网上找到答案。

我需要在运行时使用自定义名称在Python(Python2.7)中创建动态函数。每个函数的主体也需要在运行时构造,但它(几乎)对于所有函数都是相同的。

我从名单开始。

func_names = ["func1", "func2", "func3"]

请注意,func_name列表可以包含任意名称列表,因此名称不会只是func1,func2,func3,....

我希望结果是:

    def func1(*args):
        ...

    def func2(*args):
        ...

    def func3(*args):
        ...

我需要这样做的原因是每个函数名称对应一个测试用例,然后从外部世界调用。

更新:没有用户输入。我正在绑一个更大的模块的两端。一端确定测试用例是什么,以及填充测试用例名称的列表。另一端是函数本身,它必须与测试用例的名称进行1:1映射。所以我有测试用例的名称,我知道我想对每个测试用例做什么,我只需要创建具有测试用例名称的函数。由于测试用例的名称是在运行时确定的,因此基于这些测试用例的函数创建也必须在运行时。

更新:我也可以将这个自定义命名函数包装在一个类中,如果这会让事情变得更容易。

我可以在字符串中对函数的内容进行硬编码(因为它们几乎相同),或者我可以将它基于先前定义的基类。只需要知道如何用这个内容填充函数。

例如:

    func_content = """
                   for arg in args:
                       print arg
                   """

提前致谢,

马赫迪

6 个答案:

答案 0 :(得分:44)

对于你描述的内容,我认为你不需要进入eval或宏 - 通过闭包创建函数实例应该可以正常工作。例如:

def bindFunction1(name):
    def func1(*args):
        for arg in args:
            print arg
        return 42 # ...
    func1.__name__ = name
    return func1

def bindFunction2(name):
    def func2(*args):
        for arg in args:
            print arg
        return 2142 # ...
    func2.__name__ = name
    return func2

但是,您可能希望按名称将这些功能添加到某个范围,以便您可以按名称访问它们。

>>> print bindFunction1('neat')
<function neat at 0x00000000629099E8>
>>> print bindFunction2('keen')
<function keen at 0x0000000072C93DD8>

答案 1 :(得分:14)

延伸Shane的回答,因为我在寻找类似问题的解决方案时发现了这个问题。注意变量的范围。您可以通过使用生成器函数来定义范围来避免范围问题。以下是定义类的方法的示例:

class A(object):
    pass

def make_method(name):
    def _method(self):
        print("method {0} in {1}".format(name, self))
    return _method

for name in ('one', 'two', 'three'):
    _method = make_method(name)
    setattr(A, name, _method)

使用中:

In [4]: o = A()

In [5]: o.one()
method one in <__main__.A object at 0x1c0ac90>

In [6]: o1 = A()

In [7]: o1.one()
method one in <__main__.A object at 0x1c0ad10>

In [8]: o.two()
method two in <__main__.A object at 0x1c0ac90>

In [9]: o1.two()
method two in <__main__.A object at 0x1c0ad10>

答案 2 :(得分:6)

可能有一种反省做这种事情,但我不认为这是解决这个问题的pythonic方法。

我认为你应该利用python中函数的本质作为一级公民。使用Shane Holloway指出的闭包,可以根据需要自定义功能内容。然后对于动态名称绑定,使用一个字典,其键是动态定义的名称,值将是函数本身。

def function_builder(args):
    def function(more_args):
       #do stuff based on the values of args
    return function

my_dynamic_functions = {}
my_dynamic_functions[dynamic_name] = function_builder(some_dynamic_args)

#then use it somewhere else
my_dynamic_functions[dynamic_name](the_args)

希望你的用例有意义。

答案 3 :(得分:2)

您可能想要使用eval;你将构建包含每个函数的Python定义的字符串(即以def func1开头的多行字符串....)然后你将eval它。

但我会为每个这样的函数生成一个唯一的名称(例如genfun345)。请勿对此类名称使用某些未经检查的用户输入。因为如果名称与Python中的某个已知名称相同,那么您将遇到难以调试的灾难。

您是否相信这些功能的输入?您是否关心恶意软件或滥用?

在维基百科上阅读hygenic macros

答案 4 :(得分:1)

如果我正确理解您的要求,听起来您只想动态地为现有功能分配新名称或替代名称 - 在这种情况下,应遵循以下几行:

import sys

testcases = []
def testcase(f):
    """ testcase function decorator """
    testcases.append(f)
    return f

@testcase
def testcase0(*args):
    print 'testcase0 called, args:', args

@testcase
def testcase1(*args):
    print 'testcase1 called, args:', args

@testcase
def testcase2(*args):
    print 'testcase2 called, args:', args

def assign_function_names(func_names, namespace=None):
    if namespace is None:
        namespace = sys._getframe(1).f_globals  # default to caller's globals
    for name, func in zip(func_names, testcases):
        func.__name__ = name  # optional
        namespace[name] = func

assign_function_names(["funcA", "funcB", "funcC"])

funcA(1, 2, 3)
funcB(4, 5)
funcC(42)

答案 5 :(得分:0)

要真正真正地动态创建函数,可以使用makefun,我已经为此专门开发了它。它支持三种方法来定义要生成的签名:

  • 字符串表示形式,例如'foo(a, b=1)'
  • 一个Signature对象,它是手工制作的或通过在另一个函数上使用inspect.signature派生的
  • 参考功能。在这种情况下,公开的签名将与该函数的签名相同。

此外,您可以告诉它将创建的函数的引用作为实现中的第一个参数注入,以便根据调用的来源(哪个外观)进行较小的行为修改。例如:

# generic core implementation
def generic_impl(f, *args, **kwargs):
    print("This is generic impl called by %s" % f.__name__)
    # here you could use f.__name__ in a if statement to determine what to do
    if f.__name__ == "func1":
        print("called from func1 !")
    return args, kwargs

my_module = getmodule(generic_impl)

# generate 3 facade functions with various signatures
for f_name, f_params in [("func1", "b, *, a"),
                         ("func2", "*args, **kwargs"),
                         ("func3", "c, *, a, d=None")]:
    # the signature to generate
    f_sig = "%s(%s)" % (f_name, f_params)

    # create the function dynamically
    f = create_function(f_sig, generic_impl, inject_as_first_arg=True)

    # assign the symbol somewhere (local context, module...)
    setattr(my_module, f_name, f)

# grab each function and use it
func1 = getattr(my_module, 'func1')
assert func1(25, a=12) == ((), dict(b=25, a=12))

func2 = getattr(my_module, 'func2')
assert func2(25, a=12) == ((25,), dict(a=12))

func3 = getattr(my_module, 'func3')
assert func3(25, a=12) == ((), dict(c=25, a=12, d=None))

正如您在documentation中所看到的,参数总是重定向到kwargs,除非绝对不可能(例如func2中的可变位置签名)。 >