Python dynamic function creation with custom names

后端 未结 6 2238
深忆病人
深忆病人 2020-11-27 18:07

Apologies if this question has already been raised and answered. What I need to do is very simple in concept, but unfortunately I have not been able to find an answer for it

相关标签:
6条回答
  • 2020-11-27 18:44

    For what you describe, I don't think you need to descend into eval or macros — creating function instances by closure should work just fine. Example:

    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
    

    However, you will likely want to add those functions by name to some scope so that you can access them by name.

    >>> print bindFunction1('neat')
    <function neat at 0x00000000629099E8>
    >>> print bindFunction2('keen')
    <function keen at 0x0000000072C93DD8>
    
    0 讨论(0)
  • 2020-11-27 18:45

    If I'm understanding your requirements correctly, it sounds like you just want to dynamically assign existing functions new or alternative names -- in which case something along the following lines ought to do the job:

    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)
    
    0 讨论(0)
  • 2020-11-27 18:55

    Extending on Shane's answer since I just found this question when looking for a solution to a similar problem. Take care with the scope of the variables. You can avoid scope problems by using a generator function to define the scope. Here is an example that defines methods on a class:

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

    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>
    
    0 讨论(0)
  • 2020-11-27 18:56

    To truly create functions dynamically, you can use makefun, I have developed it just for that. It supports three ways to define the signature to generate:

    • a string representation, for example 'foo(a, b=1)'
    • a Signature object, either handcrafted or derived by using inspect.signature on another function
    • a reference function. In that case the exposed signature will be identical to this function's signature.

    Moreover you can tell it to inject the created function's reference as the first argument in your implementation, so as to have minor behavior modifications depending on where the call comes from (which facade). For example:

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

    As you can see in the documentation, arguments are always redirected to the kwargs except when it is absolutely not possible (var-positional signatures such as in func2).

    0 讨论(0)
  • 2020-11-27 18:59

    You may want to use eval; you'll build the string containing the Python definition of each function (i.e. a multiline string starting with def func1 ....) and you'll then eval it.

    But I would generate a unique name for each such function (e.g. genfun345). Don't use some unchecked user input for such names. Because if the name is the same as some known name in Python, you are going into a difficult to debug disaster.

    Do you trust the inputs from which these functions are made? Do you care about malware or abuse?

    Read about hygenic macros on wikipedia.

    0 讨论(0)
  • 2020-11-27 19:04

    There probably is a sort of introspection to do this kind of thing, but I don't think it would be the pythonic approach to the problem.

    I think you should take advantage of the nature of functions in python as first level citizens. Use closures as Shane Holloway pointed, to customize the function contents as you like. Then for the dynamic name binding, use a dictionary whose keys are the names defined dynamically, and the values will be the functions itself.

    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)
    

    Hope it makes sense to your use case.

    0 讨论(0)
提交回复
热议问题