Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 923
Jason S Avatar asked Dec 20 '13 21:12

Jason S


People also ask

How do you create a function argument in Python?

Here are simple rules to define a function in Python. Function blocks begin with the keyword def followed by the function name and parentheses ( ( ) ). Any input parameters or arguments should be placed within these parentheses. You can also define parameters inside these parentheses.

How get arguments name in Python?

To extract the number and names of the arguments from a function or function[something] to return ("arg1", "arg2"), we use the inspect module. The given code is written as follows using inspect module to find the parameters inside the functions aMethod and foo.

How do you specify a keyword argument in function?

Keyword arguments (or named arguments) are values that, when passed into a function, are identifiable by specific parameter names. A keyword argument is preceded by a parameter and the assignment operator, = . Keyword arguments can be likened to dictionaries in that they map a value to a keyword.

Can Python function take list as argument?

You can send any data types of argument to a function (string, number, list, dictionary etc.), and it will be treated as the same data type inside the function.


2 Answers

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)))
like image 175
martineau Avatar answered Oct 18 '22 01:10

martineau


I have a feeling you want something like this:

import inspect
import math

def multiply(x, y):
    return x * y

def add(a, b):
    return a + b

def cube(x):
    return x**3

def pythagorean_theorum(a, b, c):
    return math.sqrt(a**2 + b**2 + c**2)

def rpc_command(fname, *args, **kwargs):
    # Get function by name
    f = globals().get(fname)
    # Make sure function exists
    if not f:
        raise NotImplementedError("function not found: %s" % fname)
    # Make a dict of argname: argvalue
    arg_names = inspect.getargspec(f).args
    f_kwargs = dict(zip(arg_names, args))
    # Add kwargs to the function's kwargs
    f_kwargs.update(kwargs)
    return f(**f_kwargs)

Usage:

>>> # Positional args
... rpc_command('add', 1, 2)
3
>>> 
>>> # Keyword args
... rpc_command('multiply', x=20, y=6)
120
>>> # Keyword args passed as kwargs
... rpc_command('add', **{"a": 1, "b": 2})
3
>>> 
>>> # Mixed args
... rpc_command('multiply', 5, y=6)
30
>>> 
>>> # Different arg lengths
... rpc_command('cube', 3)
27
>>> 
>>> # Pass in a last as positional args
... rpc_command('pythagorean_theorum', *[1, 2, 3])
3.7416573867739413
>>> 
>>> # Try a non-existent function
... rpc_command('doesntexist', 5, 6)
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 6, in rpc_command
NotImplementedError: function not found: doesntexist
like image 24
Matt Williamson Avatar answered Oct 18 '22 02:10

Matt Williamson