Python—变量定义的参数的确切数目
我正在创建一个方法,该方法构造一个匿名方法来返回多变量函数,例如f(x,y,z)=b。我希望用户能够传递变量列表:Python—变量定义的参数的确切数目,python,lambda,anonymous-function,variadic-functions,Python,Lambda,Anonymous Function,Variadic Functions,我正在创建一个方法,该方法构造一个匿名方法来返回多变量函数,例如f(x,y,z)=b。我希望用户能够传递变量列表: def get_multivar_lambda(expression, variables=["x"]) 然后,我希望返回的匿名函数完全采用len(变量)参数(基于列表索引的位置或基于列表中字符串的关键字)。我知道我可以使用*args检查长度,但这似乎不雅观 这可能吗?我该怎么做 下面是我如何对一个变量执行此操作的示例(其中seval是一个from模块simple\u eval)
def get_multivar_lambda(expression, variables=["x"])
然后,我希望返回的匿名函数完全采用len(变量)
参数(基于列表索引的位置或基于列表中字符串的关键字)。我知道我可以使用*args
检查长度,但这似乎不雅观
这可能吗?我该怎么做
下面是我如何对一个变量执行此操作的示例(其中seval
是一个from模块simple\u eval
):
下面是我如何通过检查传递的参数的长度*
完成的:
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
编辑:我从用户输入中获取表达式和变量,所以最好采用一种安全的方法 您可以将表达式解析为AST。然后可以遍历AST以计算表达式。如果您明确列出希望处理的节点类型,这是安全的 例如,使用,您可以执行以下操作
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):
"""
https://stackoverflow.com/a/9558001/190597 (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
我认为您不能完全按照自己的意愿(通常使用特定数量的参数定义函数) 但是simpleeval内置了变量替换: 因此,要吸取的教训是:
- 寻找其他方法来得到你想要的
- 调用函数时引发异常(由于解释器发现错误数量的参数)和内部引发异常之间没有太大区别
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
如果您可以使用Python 3,那么新引入的(Python 3.3+)将使您的代码非常干净()。这些在装饰师中也非常方便:
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
演示:
>>> 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
这样的事情肯定是可能的。我已经使用
ast
编写了一个解决方案。它比其他解决方案要详细一点,但是返回的对象是一个不需要任何中间编译步骤的函数,例如使用simple\u 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
导入ast
def get_multi_lambda(expr,args=()):
代码\u stmt=ast.parse(expr,mode='eval')
收集器=名称收集器()
催收员访问(代码)
arg_set=set(args)
如果arg_set-collector.names:
raise TypeError(“未使用的参数”,参数集-收集器.名称)
elif collector.names-参数集:
#非常热心,旨在停止执行任意代码
#--防止使用不是函数参数的*any*名称
#--不幸的是,这种幼稚的方法也阻止了像sum这样的事情
raise TypeError(“尝试的非本地名称访问”,
collector.names-arg_集)
func_节点=创建函数节点(参数,代码)
code_obj=编译(函数节点,“,”评估“)
返回eval(代码_obj,{},{})
定义创建函数节点(参数、代码):
lambda_args=ast.arguments(
args=[ast.arg(name,None)表示args中的名称],
vararg=None,varargannotation=None,kwonlyargs=[],kwarg=None,
kwargannotation=None,默认值=[],kw_默认值=[]
)
func=ast.Lambda(args=Lambda\u args,body=code\u stmt.body)
expr=ast.Expression(func)
修复缺少的位置(扩展)
返回表达式
类名收集器(ast.NodeVisitor):
“”“查找ast节点树使用的所有名称。”“”
定义初始化(自):
self.names=set()
def visit_名称(自身、节点):
self.names.add(node.id)
#示例用法
func=get_multi_lambda('a/b+1',['a','b']))
打印(func(3,4))#在python 3中打印1.75
如果您可以信任这些多lambda表达式的源,或者您可以为您认为合适的某些名称添加异常,则可以选择排除“第二个名称”检查。例如,
min
,max
,sum
,等等。如果将表达式作为用户的文本输入,这是危险的吗?是的,执行用户输入是危险的。有没有一种安全的方法来执行我的请求,或者这是非常危险的?为什么要默默地删除未使用的变量?这是一个失败点:我将传入一个包含未使用变量的表达式,然后传入这些变量的参数,得到一个令人困惑的错误。要么忽略未使用的变量,要么对未使用的变量进行raise
。另外,您可以使用SymPy吗?这一点很好!我稍后会解决这个问题。可能吧,但这只是学习经验的一部分!具体地说,学习使用Python的功能我能做什么和不能做什么。我将您的循环更改为使用zip
,因为星号在错误的位置。另外,请尝试替换操作。如何添加其他功能,如min
、max
、sum
,等等?默认情况下它们都在那里。摆脱抛出异常的elif
分支是最简单的方法。更安全的方法可能是将elif子句更改为类似以下内容:collector.names-arg_set-set(['min','max','sum',…])
。
>>> 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)
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