[SOLVED] How to create a function at runtime with specified argument names?

Issue

Suppose I have this function:

def f(x,y):
   return x+y

If I use inspect.getargspec(f).args I get ['x','y'] as a result. Great.

Now suppose I want to create another function g(a,b) at runtime, where I don’t know the argument names a and b until runtime:

def g(a,b):
   return f(a,b)

Is there a way to do this? Lambdas are almost right, except I can only assign argument names at compile time.

g = lambda *p: f(*p)

Somehow I want to create the function dynamically at run time based on a list L (for example L=['a','b']), so that inspect.getargspec(g).args == L).

Solution

Here’s a somewhat hacky way to do it which first creates a new function from an existing one with the modification and then replaces the original’s code with it. It’s lengthly mostly because the types.CodeType() call has so many arguments. The Python 3 version is somewhat different because a number of the function.func_code attributes were renamed and the calling sequence of types.CodeType() was changed slightly.

I got the idea from this answer by @aaronasterling (who says he got the idea from Michael Foord’s Voidspace blog entry #583 titled Selfless Python). It could easily be made into a decorator, but I don’t see that as being helpful based on what you’ve told us of the intended usage.

import sys
import types

def change_func_args(function, new_args):
    """ Create a new function with its arguments renamed to new_args. """

    if sys.version_info[0] < 3:  # Python 2?
        code_obj = function.func_code
        assert(0 <= len(new_args) <= code_obj.co_argcount)
        # The arguments are just the first co_argcount co_varnames.
        # Rreplace them with the new argument names in new_args.
        new_varnames = tuple(new_args[:code_obj.co_argcount] +
                             list(code_obj.co_varnames[code_obj.co_argcount:]))
        new_code_obj = types.CodeType(code_obj.co_argcount,
                                      code_obj.co_nlocals,
                                      code_obj.co_stacksize,
                                      code_obj.co_flags,
                                      code_obj.co_code,
                                      code_obj.co_consts,
                                      code_obj.co_names,
                                      new_varnames,
                                      code_obj.co_filename,
                                      code_obj.co_name,
                                      code_obj.co_firstlineno,
                                      code_obj.co_lnotab,
                                      code_obj.co_freevars,
                                      code_obj.co_cellvars)
        modified = types.FunctionType(new_code_obj, function.func_globals)

    else:  # Python 3
        code_obj = function.__code__
        assert(0 <= len(new_args) <= code_obj.co_argcount)
        # The arguments are just the first co_argcount co_varnames.
        # Replace them with the new argument names in new_args.
        new_varnames = tuple(new_args[:code_obj.co_argcount] +
                             list(code_obj.co_varnames[code_obj.co_argcount:]))

        new_code_obj = types.CodeType(code_obj.co_argcount,
                                      code_obj.co_posonlyargcount,
                                      code_obj.co_kwonlyargcount,
                                      code_obj.co_nlocals,
                                      code_obj.co_stacksize,
                                      code_obj.co_flags,
                                      code_obj.co_code,
                                      code_obj.co_consts,
                                      code_obj.co_names,
                                      new_varnames,
                                      code_obj.co_filename,
                                      code_obj.co_name,
                                      code_obj.co_firstlineno,
                                      code_obj.co_lnotab)

        modified = types.FunctionType(new_code_obj, function.__globals__)

    function.__code__ = modified.__code__  # replace code portion of original

if __name__ == '__main__':

    import inspect

    def f(x, y):
        return x+y

    def g(a, b):
        return f(a, b)

    print('Before:')
    print('inspect.getargspec(g).args: {}'.format(inspect.getargspec(g).args))
    print('g(1, 2): {}'.format(g(1, 2)))

    change_func_args(g, ['p', 'q'])

    print('')
    print('After:')
    print('inspect.getargspec(g).args: {}'.format(inspect.getargspec(g).args))
    print('g(1, 2): {}'.format(g(1, 2)))

Answered By – martineau

Answer Checked By – David Marino (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published.