57

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

我需要在运行时使用自定义名称在 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
                   """

提前致谢,

马赫迪

4

6 回答 6

61

对于您所描述的,我认为您不需要深入到 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>
于 2012-11-01T19:56:33.277 回答
22

扩展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>
于 2013-10-30T20:21:29.583 回答
8

做这种事情可能有一种内省,但我认为这不是解决问题的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)

希望它对您的用例有意义。

于 2012-11-01T20:18:14.403 回答
3

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

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

你相信这些函数的输入吗?您是否关心恶意软件或滥用行为?

在维基百科上阅读卫生宏

于 2012-11-01T19:42:27.873 回答
2

要真正动态地创建函数,您可以使用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))

正如您在文档中看到的那样,参数总是被重定向到kwargs除非绝对不可能(var-positional 签名,例如 in func2)。

于 2019-03-11T16:03:16.840 回答
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)
于 2012-11-01T21:59:24.823 回答