【问题标题】:Python dynamic function creation with custom names使用自定义名称创建 Python 动态函数
【发布时间】:2012-10-22 11:10:41
【问题描述】:

如果这个问题已经被提出并回答,我们深表歉意。 我需要做的概念很简单,可惜我一直没能在网上找到答案。

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

提前致谢,

马赫迪

【问题讨论】:

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


    【解决方案1】:

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

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

    您是否信任生成这些函数的输入?您是否关心恶意软件或滥用行为?

    在维基百科上了解hygenic macros

    【讨论】:

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

    对于您所描述的,我认为您不需要深入到 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>
    

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        如果我正确理解了您的要求,听起来您只是想动态地为现有函数分配新名称或替代名称——在这种情况下,应该按照以下几行来完成这项工作:

        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)
        

        【讨论】:

        • 感谢您的回复。但事实并非如此。它们不是现有函数,而且要动态创建的函数的数量也是未知的(仅在运行时知道)。
        • 如果您可以“对函数的内容进行硬编码”,您不妨在 .py 文件中在该内容之前加上 def xxx(yyy): 并使其成为现有函数——您怎么看您通过将其放入字符串并从中动态创建函数来获得收益?
        • 我认为您误解了我在这里尝试做的事情,并没有真正回答我的问题,而是一直告诉我要做其他事情。不过还是谢谢。我已经解决了我的问题。
        • @mahdiolfat:显然我不是唯一一个不明白的人。如果您事先知道函数的内容,您没有回答我为什么需要动态创建函数的问题。动态更改它们的名称并使其成为方法是一种更容易理解的需求,并且相对容易完成。
        • 我的问题是如何创建动态函数。这不是关于为什么需要做一个。如果你知道怎么做,请告诉我。如果没有,不要浪费你的时间。
        【解决方案5】:

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

        【讨论】:

        • 如何在类__init__中添加这个?不在课外。
        • @AlexeiMarinichenko 将其添加到 init__ 会使其特定于实例,这不是一个好主意。如果您需要特定于实例的行为,我会考虑专门化 __getattr 或使用类中定义的一些不同的协议。避免使用这种深奥的东西,除非你真的需要它,如果你真的需要它,要格外小心,让它尽可能清晰,否则它会回来咬你。
        • @AlexeiMarinichenko 也就是说,在类规范之后添加方法是“不整洁的”,如果这是您所关心的,我只会将类保留在它自己的模块中。如果在您的应用程序中为多个类执行此操作,我会考虑使用元类来专门化类实例化,以便您可以将动态方法规范移动到类变量中。
        【解决方案6】:

        要真正动态地创建函数,你可以使用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,除非绝对不可能(var-positional 签名,例如func2)。

        【讨论】:

          猜你喜欢
          • 2012-10-23
          • 1970-01-01
          • 2023-03-22
          • 1970-01-01
          • 2013-01-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多