Python - exact number of arguments defined by a variable

I create a method that creates an anonymous method to return a function from several variables, for example. 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"])

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

Is it possible? How can i do this?

Here is an example of how I did this for a single variable (where sevalis the module simple_eval):

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

And this is how I did it, just checking the length of the transmitted arguments*:

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: , .

+4
5

Python 3, (Python 3.3+) inspect.Signature inspect.Parameter (PEP 362 - ). :

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

:

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

:

>>> inspect.getargspec(g)
ArgSpec(args=['x', 'y', 'z'], varargs=None, keywords=None, defaults=None)
+5

AST. AST, . , node, .

, J.F. , -

import ast
import operator as op
import textwrap
def make_func(expression, variables):
    template = textwrap.dedent('''\
        def func({}):
            return eval_expr({!r}, locals())
        ''').format(','.join(variables), expression)
    namespace = {'eval_expr':eval_expr}
    exec template in namespace
    return namespace['func']


def eval_expr(expr, namespace):
    """
    >>> eval_expr('2^6')
    4
    >>> eval_expr('2**6')
    64
    >>> eval_expr('1 + 2*3**(4^5) / (6 + -7)')
    -5.0
    """
    # Module(body=[Expr(value=...)])
    return eval_(ast.parse(expr).body[0].value, namespace)  


def eval_(node, namespace=None):
    """
    /questions/80084/evaluating-a-mathematical-expression-in-a-string/530326#530326 (J.F. Sebastian)
    """
    if namespace is None:
        namespace = dict()
    if isinstance(node, ast.Num):  # <number>
        return node.n
    elif isinstance(node, ast.operator):  # <operator>
        return operators[type(node)]
    elif isinstance(node, ast.BinOp):  # <left> <operator> <right>
        return eval_(node.op, namespace)(eval_(node.left, namespace),
                                         eval_(node.right, namespace))
    elif isinstance(node, ast.Name):
        return namespace[node.id]
    else:
        raise TypeError(node)

operators = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul,
             ast.Div: op.truediv, ast.Pow: op.pow, ast.BitXor: op.xor,
             ast.USub: op.neg}

f = make_func('x', ['x'])
print(f(2))
# 2

g = make_func('x+y+z', ['x','y','z'])
print(g(1,2,3))
# 6   

:

f = make_func('x', ['x'])
print(f(2))
# 2

g = make_func('x+y+z', ['x','y','z'])
print(g(1,2,3))
# 6
+1

, , ( ).

simpleeval , : https://pypi.python.org/pypi/simpleeval#names

, , :

  • , .
  • (- , ) .
+1

, .

from simpleeval import simple_eval as seval



class MultivarLambda(object):
    def __init__(self, expression, variables):
        self.__expression = expression
        self.__variables = variables


    def __call__(self, *args):
        line = self.__expression

        for v, arg in zip(self.__variables, args):
            line = line.replace(v, arg)

        return seval(line)



f = MultivarLambda("(A)**2 + (B)**2", ["A", "B"])

print f('3', '4')
print f('5', '-12')

# 25
# 169
+1

. , ast. , , - , - , simple_eval.

import ast

def get_multi_lambda(expr, args=()):
    code_stmt = ast.parse(expr, mode='eval')

    collector = NameCollector()
    collector.visit(code_stmt)

    arg_set = set(args)
    if arg_set - collector.names:
        raise TypeError("unused args", arg_set - collector.names)
    elif collector.names - arg_set:
        # very zealous, meant to stop execution of arbitrary code 
        # -- prevents use of *any* name that is not an argument to the function
        # -- unfortunately this naive approach also stops things like sum
        raise TypeError("attempted nonlocal name access", 
            collector.names - arg_set)

    func_node = create_func_node(args, code_stmt)
    code_obj = compile(func_node, "<generated>", "eval")
    return eval(code_obj, {}, {})

def create_func_node(args, code_stmt):
    lambda_args = ast.arguments(
        args=[ast.arg(name, None) for name in args],
        vararg=None, varargannotation=None, kwonlyargs=[], kwarg=None,
        kwargannotation=None, defaults=[], kw_defaults=[]
    )
    func = ast.Lambda(args=lambda_args, body=code_stmt.body)
    expr = ast.Expression(func)
    ast.fix_missing_locations(expr)
    return expr

class NameCollector(ast.NodeVisitor):
    """Finds all the names used by an ast node tree."""

    def __init__(self):
        self.names = set()

    def visit_Name(self, node):
        self.names.add(node.id)

# example usage
func = get_multi_lambda('a / b + 1', ['a', 'b'])
print(func(3, 4)) # prints 1.75 in python 3

, -- , , , . . min, max, sum ..

+1

Source: https://habr.com/ru/post/1569152/


All Articles