2017-06-12 77 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