2017-06-12 34 views
3

这是一个关于编程风格的问题:什么是最“Python化”的方式来参数的函数(?是,即使它正确的字)参数化功能(不拉姆达)Python的方式

说我有一个函数(例如ODE求解器),它接受两个参数的另一个函数(例如ODE本身)作为参数。

def solver(fun): 
    # Implementation goes here 
    # fun is a function handle that accepts two args e.g. fun(t,y) 

不过,我想进入solver功能由第三值

def myFun(t,y,A): 
    # Implementation goes here 

我一直在处理使用lambda功能这种情况如下参数:

A = 4 
solution = solver(lambda t,y:myFun(t,y,A)) 

我最近在网上看到一些帖子,告诉我要避免lambda这样的瘟疫,而且圭多本人很遗憾允许功能。如果lambda确实很糟糕,那么实施上述的更好的“Pythonic”方法是什么?如果没有lambda我碰上了无法访问的全局命名空间的问题,也就是我想这样做:

A = 4 
def myFunNotLambda(t,y): 
    return myFun(t,y,A) 
solution = solver(myFunNotLambda) 

,但要做到这一点的唯一方法似乎是使A全球,这绝对是有过之而无不及远使用lambda

+2

还有'functools.partial' –

+3

有没有必要避免像鼠疫lambdas。也就是说,'def'可以做任何'lambda';你只是忘了'返回'的价值。 'functools.partial'也可以工作,尽管它不能修正前面不存在的位置参数。 – user2357112

+0

Guido对'lambda'的主要反对意见是当人们用它来包装一个简单的表达式来传递给map(或filter)而不是直接在列表comp或gen exp中执行表达式,例如'map(lambda x :5 * x + 1,seq)'vs'(5 * x + 1 for x in seq)''。请注意,'map'version会引发'seq'中每个项目的Python函数调用的开销。当然,如果传递给'map'的可调用函数在C中实现,例如'map(int,list_of_strings)'完全没问题,那么这是无关紧要的。 –

回答

3

建议“避免lambda像瘟疫”充其量是个严重的夸张,而纯FUD在最坏的情况 - grepping lambda在Python的标准库揭示字面上成千上万的比赛

虽然这是事实,圭多具有expressed later concern约拉姆达为主的编码风格,这是在(过)使用功能结构,如mapreduce,这是在使用Python列表内涵和发电机表现更好的表达语境。

但是,如果需要创建临时功能(如所需的临时功能),则使用lambda确实没有任何问题;相反,它是这项工作最好的工具。如果你仍然想避免lambda关键字,您可以使用嵌套def

def my_particular_fun(t, y): 
    return my_fun(t, y, 4) 

solution = solver(my_particular_fun) 

在其他的答案提出,也可以使用functools.partial效仿拉姆达,虽然它是在固定的成本第三个参数的名称为my_fun

+1

'my_particular_fun'的问题是它会产生双重函数调用的代价,而且Python调用相对较慢。 –

+0

@ PM2Ring除了重写'my_fun'以将'A'参数硬编码为4以外,任何解决方案都是这种情况。另外,担心单个附加*函数调用的速度*而不知道函数是什么这听起来像是过早的优化。 – user4815162342

+0

假设解算者在一个循环中调用其“有趣”参数,所以将涉及的调用次数减半可能会导致明显的速度差异。 –

5

您可以使用functools.partial为,如:给出一个函数(这里myFunc

from functools import partial 

A = 4 
solution = solver(partial(myFun,A=A))

partial(..)构建另一个functi在A参数现在具有默认值A

+1

+1这很好地解耦了传递参数给调用者函数的任何逻辑。此外,这增加了'solver'的灵活性,有效地允许'myFun'接受任意数量的常量参数。 – jpmc26

+0

构建这个函数的开销是否与构造'lambda'的开销相同? – dkv

+2

@dkv:构造还是调用?因为通常建造只发生一次,因此并不重要。 [这个答案](https://stackoverflow.com/a/11828489/67579)表明partial比lambda更快。 –

1

一个相当有效的方法是使用functools.partial,但正如已经指出的,partial只允许你“冻结”最终的参数。如果您需要其他的东西,您可以使用closure轻松实现您自己的版本。

这种做法实际上是比使用partial对象,有点更高效,当你调用它仍然叫你通过在原有的功能,所以每两个呼叫partial结果的通话partial以来,和Python函数/方法调用相对较慢。如果你很好奇,看看the Python source code for functools.partial

在这个例子中,我将A作为函数的第二个(伪)参数,因为partial很好地处理了它是最后一个参数的情况。

def my_partial(param): 
    A = param 
    print('Creating two_arg_func with A ==', A) 
    def two_arg_func(t, y): 
     # Do something with all the args 
     return 'args', t, A, y 
    return two_arg_func 

def test(f): 
    for u in range(10, 50, 10): 
     print(f(u, u + 5)) 

test(my_partial(7)) 

输出

Creating two_arg_func with A == 7 
('args', 10, 7, 15) 
('args', 20, 7, 25) 
('args', 30, 7, 35) 
('args', 40, 7, 45) 

我们并不真正需要my_partialparam,我们可以只使用ARG传递,因为这是本地my_partial

def my_partial(A): 
    print('Creating two_arg_func with A ==', A) 
    def two_arg_func(t, y): 
     return 'args', t, A, y 
    return two_arg_func 

从您的意见,我现在明白,你想能够改变A。当然,您可以通过再次拨打partialmy_partial来完成此操作,但是如果您想修改A,那么效果不佳。

您的意见表明您想要在全球范围内修改A,所以您不妨使用全局。您不需要将代码修改为A到全局上下文中,您可以将其包装在一个函数中,但当然需要在修改A的函数中使用global指令。不过,您需要而不是需要指令global,它只读取A的值。

这是一个简短的演示。

def two_arg_func(t, y): 
    # Do something with the args and with A 
    return 'args', t, A, y 

def solve(f): 
    for u in range(10, 40, 10): 
     print('SOLVER', f(u, u + 5)) 

def test(f): 
    global A 
    for A in range(7, 10): 
     print(A) 
     solve(f) 

test(two_arg_func) 

输出

7 
SOLVER ('args', 10, 7, 15) 
SOLVER ('args', 20, 7, 25) 
SOLVER ('args', 30, 7, 35) 
8 
SOLVER ('args', 10, 8, 15) 
SOLVER ('args', 20, 8, 25) 
SOLVER ('args', 30, 8, 35) 
9 
SOLVER ('args', 10, 9, 15) 
SOLVER ('args', 20, 9, 25) 
SOLVER ('args', 30, 9, 35) 

然而,以前的解决方案是有点不满意,因为你的问题的主旨是如何使用全局为此没有。所以这里有一些代码的细微变化。我们不把A放在全局名称空间中,而是将它作为函数属性附加到two_arg_func。我们可以这样做,因为Python函数是一个对象,并且它已经有很多属性;两个你可能熟悉的是__name____doc__。无论如何,这是新代码,它打印出与以前版本相同的输出。

def two_arg_func(t, y): 
    A = two_arg_func.A 
    # Do something with the args and with A 
    return 'args', t, A, y 

def solve(f): 
    for u in range(10, 40, 10): 
     print('SOLVER', f(u, u + 5)) 

def test(f): 
    for A in range(7, 10): 
     print(A) 
     f.A = A 
     solve(f) 

test(two_arg_func) 
+0

不是'my_partial'有双重电话的问题吗?即当你调用'test'时,它会调用'my_partial'四次,并且这四次中的每一个都定义并立即调用'two_arg_func'? – dkv

+1

@dkv不,我的代码'my_partial'只在最后一行调用一次。 'two_arg_func'只在'Creating two_arg_func with A == 7'被打印后才被定义一次(实际上,我在那里添加了'print'调用来证明这一点)。如果你再次调用'my_partial',只会创建一个新的函数,例如,如果你需要一个不同的'A'值。 'test'中的循环产生了4次调用,这些调用直接返回到由'my_partial(7)'调用返回的函数。 –

+0

哦,对,对不起,印刷声明确实说得很清楚。这是一些内置的Python魔法,还是有一些明显的我失踪? – dkv