Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - Exact Number of Arguments Defined by Variable

I am creating a method that constructs an anonymous method to return a function of multiple variables e.g. f(x, y, z) = b. I want the user to be able to pass a list of variables:

def get_multivar_lambda(expression, variables=["x"])

I then want the returned anonymous function to take exactly len(variables) arguments (either positional based on their list index or keyword based on the string in the list). I know I can use *args and check the length, but this seems inelegant.

Is this possible? How might I do this?

Here is an example of how I did it for one variable (where seval is a from module simple_eval):

def get_lambda(expression, variable="x"):                                       
    return lambda arg: seval(expression.replace(variable, str(arg))) 

And here's how I did it by just checking the length of the arguments* passed:

def get_multivar_lambda(expression, variables=["x"]):

    def to_return(*arguments):
        if len(variables) != len(arguments):
            raise Exception("Number of arguments != number of variables")
        for v, a in zip(variables, arguments):
            expression.replace(v, a)
        return seval(expression)

    return to_return

EDIT: I am taking expression and variables from user input, so a safe way to do this would be best.

like image 328
Langston Avatar asked Dec 19 '22 08:12

Langston


1 Answers

If you can use Python 3 then the newly introduced(Python 3.3+) inspect.Signature and inspect.Parameter can make your code very clean(PEP 362 - Function Signature Object). These come very handy in decorators as well:

from inspect import Parameter, signature, Signature

def get_multivar_lambda(expression, variables=["x"]):

    params = [Parameter(v, Parameter.POSITIONAL_OR_KEYWORD) for v in variables]
    sig = Signature(params)

    def to_return(*args, **kwargs):
        values = sig.bind(*args, **kwargs)
        for name, val in values.arguments.items():
            print (name, val)

    to_return.__signature__ = signature(to_return).replace(parameters=params)
    return to_return

Demo:

>>> f = get_multivar_lambda('foo')
>>> f(1)
x 1
>>> f(1, 2)
Traceback (most recent call last):
  File "<pyshell#43>", line 1, in <module>
  ...
    raise TypeError('too many positional arguments') from None
TypeError: too many positional arguments
>>> f(x=100)
x 100

Will produce useful error messages for user as well:

>>> g = get_multivar_lambda('foo', variables=['x', 'y', 'z'])
>>> g(20, 30, x=1000)
Traceback (most recent call last):
  File "<pyshell#48>", line 1, in <module>
    ....
TypeError: multiple values for argument 'x'
>>> g(1000, y=2000, z=500)
x 1000
y 2000
z 500

Function signature for introspection purpose:

>>> inspect.getargspec(g)
ArgSpec(args=['x', 'y', 'z'], varargs=None, keywords=None, defaults=None)
like image 50
Ashwini Chaudhary Avatar answered Dec 28 '22 10:12

Ashwini Chaudhary