为什么`scipy`接受目标函数的单独`args`?

时间:2015-11-08 11:31:07

标签: python api lambda scipy

almost forever以来,Python已经有了lambda表达式。但是,scipy和其他一些库遵循different paradigm,同时接受函数作为参数,例如

def minimize(fun, x0, args=(), [...]):
'''
where:
     args : tuple, optional
            Extra arguments passed to the objective function [...]
'''
...

,他们接受额外的参数作为单独的列表/元组fun传递给args,而不是鼓励使用lambda函数。这似乎是在代码中可以避免的另一个理由。

为什么会这样? lambda函数是否较慢?或者它只是遵循其他语言的惯例,例如R(可能会问同一个问题并指向S)?

如果我正在设计一个新的API,那么在选择这个选择方面是否有优势?

3 个答案:

答案 0 :(得分:1)

这很大一部分原因是因为Python有半个lambda"而不是真正完整的lambdas,就像你可能习惯于来自lisp或Erlang。

Python lambdas仅限于单个表达式*,不能包含某个内置的副作用语句,并且不能扩展为多行而不将它们包含在parens中。命名函数可以自由传递,可以做任何你想象的事情。在具有语义空白的语言中,很少有动机来调整语法的其余部分以适应任意复杂度的lambda。

最终,Python lambdas在某些情况下可能很方便,但它们根本不是设计任意强大的

可能通过parens和line扩展来编写大毛羔羊,但非常混乱和unpythonic。这些天甚至在Erlang中有一种趋势,经过多年的漂亮疯狂的lambdas到位,只使用lambda定义将一些局部状态包含在对外部定义函数的调用中。以这种方式使用lamdbas语言(或lisp)lambda最佳点,并在崩溃时提供更好的跟踪。 )

所以妥协与我们在Python其他地方找到的一样:明确定义函数,然后根据需要传递它们。

至于为什么scipy使用这种特殊的参数传递技术,我想这是因为他们想让你定义和使用任意函数或任意arity,这对于命名更容易 功能比其他Python程序员讨厌你定义的大疯狂就地lambdas。

相关:

[*我错误地写了#34;单个参数"之前,罗伯特克恩纠正了我。]

答案 1 :(得分:1)

是的,lambda函数可能更慢,特别是如果内部函数是C扩展函数,通常就是这种情况。该函数在最小化器内部的相对紧密的循环内调用,因此削减额外的Python函数调用的开销可以提供帮助。

functools.partial()是比lambda函数更好的替代方法,可能比将args=传递给minimize()更清晰或更方便,但当它们不存在时,它们并不存在API是多年前制作的。

答案 2 :(得分:1)

当我查看optimize.minimize代码时,我发现它将任务传递给_minimize_neldermead等函数。这些调用反过来如下:

fcalls, func = wrap_function(func, args)
<more setup>
... = func(x0)
... = func(y)

(来自scipy/optimize/optimize.py):

def wrap_function(function, args):
    ncalls = [0]
    if function is None:
        return ncalls, None

    def function_wrapper(*wrapper_args):
        ncalls[0] += 1
        return function(*(wrapper_args + args))

    return ncalls, function_wrapper

所以args通过将它们连接到变量(wrapper_args)并将元组传递给你的函数来处理。这是一个简单,直接的包装机制。

.py文件的标头为

# optimize.py module by Travis E. Oliphant
...
# A collection of optimization algorithms.  Version 0.5
# CHANGES
#  Added fminbound (July 2001)

Travis是numpy大部分的原创开发人员。请注意改变的早期年份。我假设在github存储库上安装scipy之前很久就添加了这个包装器,但你当然可以检查。

描述包装功能如何解释他选择它的原因。可能是他在FORTRAN和C包之后对API进行建模。其中一些scipy优化,ode和插值函数最终使用已编译的代码。

我发现了2013年的pull请求改变了这个包装函数的语法:

-    def function_wrapper(x):
+    def function_wrapper(*wrapper_args):
         ncalls[0] += 1
-        return function(x, *args)
+        return function(*(wrapper_args + args))

https://github.com/scipy/scipy/commit/cf3adca80e371fd19a34b398d2f1ed0e19f0cbdc

https://github.com/scipy/scipy/issues/3785

显然有些人在将args定义为列表或单个项目而不是元组时遇到了问题。有些问题是人们使用args=(x)而不是args=(x,)。此issue中提及的替代方法是functools.partial