Python: Neden functools.partial gerekli?


193

Kısmi uygulama iyidir. functools.partialLambdas'dan geçemediğiniz işlevler nelerdir ?

>>> sum = lambda x, y : x + y
>>> sum(1, 2)
3
>>> incr = lambda y : sum(1, y)
>>> incr(2)
3
>>> def sum2(x, y):
    return x + y

>>> incr2 = functools.partial(sum2, 1)
>>> incr2(4)
5

Bir functoolsşekilde daha verimli veya okunabilir mi?

Yanıtlar:


266

functools.partialLambdas'dan geçemediğiniz işlevler nelerdir ?

Ekstra işlevsellik açısından çok fazla değil (ancak daha sonra bakınız) - ve okunabilirlik bakanın gözündedir.
Fonksiyonel programlama dillerine aşina olan çoğu insan (özellikle Lisp / Şema ailelerinde olanlar) lambdagayet iyi görünüyor - "en" diyorum, kesinlikle hepsi değil , çünkü Guido ve ben kesinlikle "aşina" olanlar arasındayız (vb. ) yine lambdade Python'da göze batan bir anomali olarak düşünün ... Python'a
kabul etmeyi tövbe ederken, Python 3'ten "Python'un aksaklıklarından" biri olarak çıkarmayı planladı.
Onu tam olarak destekledim. ( lambda Scheme'de seviyorum ... Python'daki sınırlamaları ve sadece garip yolu dilin geri kalanıyla, cildimi tara.

O kadar ki, ordularını lambdaseverler - Guido backtracked ve ayrılmaya karar verene kadar, hiç Python'un tarihinin görülen bir isyan en yakın şeylerden biri sahnelenen lambda. İçinde
için çeşitli mümkün eklemeler functools(fonksiyonlar sabitler, kimlik dönen yapmak, vs) olmadı (tabii ki daha fazla lambdaişlevselliğinin açıkça çoğaltılmasını önlemek için ), partialtabii ki kaldı (bu tamamen kopya değil, ne de göze batan bir şey değil).

lambdaVücudunun bir ifade olmakla sınırlı olduğunu unutmayın , bu yüzden sınırlamaları var. Örneğin...:

>>> import functools
>>> f = functools.partial(int, base=2)
>>> f.args
()
>>> f.func
<type 'int'>
>>> f.keywords
{'base': 2}
>>> 

functools.partial's döndürülen işlevi içgözlem için yararlı özniteliklerle süslenmiştir - kaydırma işlevi ve içinde hangi konumsal ve adlandırılmış bağımsız değişkenleri düzeltir. Ayrıca, adlandırılan argümanlar hemen geri alınabilir ("sabitleme" bir anlamda varsayılanların ayarıdır):

>>> f('23', base=10)
23

Gördüğünüz gibi Yani, bu kadar tanımlık kadar basit değil lambda s: int(s, base=2)! -)

Evet, olabilir bu bazılarını vermek için lambda çarpıtmak - anahtar kelime-geçersiz kılma için, örneğin,

>>> f = lambda s, **k: int(s, **dict({'base': 2}, **k))

ama umarım en ateşli lambda-lover bile bu dehşeti partialçağrıdan daha okunabilir saymaz ! -). "Öznitelik ayarı" bölümü, Python'un "gövde tek bir ifadesi" sınırlaması lambda(ayrıca, atamanın hiçbir zaman bir Python ifadesinin parçası olamayacağı gerçeği) nedeniyle daha da zorlaşır ... "bir ifade içindeki taklit ödevleri" liste kavrayışını tasarım sınırlarının çok ötesine taşıyarak ...:

>>> f = [f for f in (lambda f: int(s, base=2),)
           if setattr(f, 'keywords', {'base': 2}) is None][0]

Şimdi adlandırılmış argümanların geçersiz kılınabilirliğini ve üç özniteliğin ayarını tek bir ifadede birleştirin ve bana bunun ne kadar okunabilir olacağını söyleyin ...!


2
Evet, functools.partialbahsettiğiniz ekstra işlevselliğin onu lambda'dan daha üstün kıldığını söyleyebilirim. Belki de bu başka bir gönderinin konusudur, ancak tasarım düzeyinde sizi bu kadar rahatsız eden şey lambdanedir?
Nick Heiner

11
@Rosarch, dediğim gibi: birincisi, bu sınırlamalar (Python keskin ifadeler ve ifadeleri ayırt - yapamayacağınız şey var ya yapamaz mantıklı tek bir ifade içinde, ve en ne lambda bedeni o ise ); ikincisi, kesinlikle garip sözdizimi şekeri. Zamanda geri gidip Python içindeki bir şeyi değiştirebilseydim, saçma, anlamsız, göze batan defve lambdaanahtar kelimeler olurdu : ikisini de yap function(bir isim seçimi Javascript gerçekten doğru oldu) ve itirazlarımın en az 1 / 3'ü yok olacaktı ! -). Dediğim gibi Lisp'te lambda'ya itirazım yok ...!)
Alex Martelli

1
@ Marlexli, Guido neden lambda için böyle bir sınırlama getirdi: "beden tek bir ifade"? C # 'nin lambda gövdesi bir işlevin bedeninde geçerli herhangi bir şey olabilir. Guido neden python lambda sınırlamasını kaldırmıyor?
Peter Long

3
@PeterLong Umarım Guido sorunuza cevap verebilir. Bunun özü, çok karmaşık olması ve defyine de kullanabilmeniz . Yardımsever liderimiz konuştu!
yeni123456

5
@AlexMartelli DropBox, Guido üzerinde ilginç bir etkiye sahip - twitter.com/gvanrossum/status/391769557758521345
David

83

İşte fark gösteren bir örnek:

In [132]: sum = lambda x, y: x + y

In [133]: n = 5

In [134]: incr = lambda y: sum(n, y)

In [135]: incr2 = partial(sum, n)

In [136]: print incr(3), incr2(3)
8 8

In [137]: n = 9

In [138]: print incr(3), incr2(3)
12 8

Ivan Moore'un bu yazıları "lambda sınırlamaları" ve python'daki kapanışlar üzerinde genişliyor:


1
İyi örnek. Bana göre, bu daha çok lambda ile bir "hata" gibi görünüyor, ama diğerlerinin aynı fikirde olamayacağını anlıyorum. (Çeşitli programlama dillerinde uygulandığı gibi, bir döngü içinde tanımlanan kapaklarda da benzer bir şey olur.)
ShreevatsaR

28
Bu "erken ve geç bağlanma ikilemi" nin çözümü, istediğiniz zaman, açıkça erken bağlamayı kullanmaktır lambda y, n=n: .... Geç (görünen isimlerin bağlayıcı sadece değil onun içinde, bir işlevin vücudunda defveya eşdeğeri lambda) bir şey ama bir hata, geçmişte uzun SO cevapları uzun uzadıya gösterilen ettik: Eğer erken bağlamak istediğin ne açıkça ne zaman, zaman geç bağlama varsayılan kullanmak olduğunu ne istediğinizi olduğunu ve bu tam olarak Python'un tasarımının dinlenme bağlam verilen sağ tasarım seçim.
Alex Martelli

1
@Alex Martelli: Evet, üzgünüm. Sadece geç bağlamaya düzgün bir şekilde alışık değilim, belki de aslında bir şeyleri iyi tanımladığım fonksiyonları tanımlarken ve beklenmedik sürprizler sadece başım ağrıyor. (Javascript'te Python'dan daha çok fonksiyonel şeyler yapmaya çalıştığımda daha fazla.) Birçok insanın geç bağlanma konusunda rahat olduğunu ve Python'un tasarımının geri kalanıyla tutarlı olduğunu anlıyorum. Yine de diğer uzun SO cevaplarınızı okumak istiyorum - bağlantılar? :-)
ShreevatsaR

3
Alex haklı, bu bir hata değil. Ama birçok lambda tutkununu hapseden bir "gotcha". : Bir HASKEL / işlevsel türünden Tartışmanın "böcek" tarafı için, Andrej Bauer'in yayını görmesini math.andrej.com/2009/04/09/pythons-lambda-is-broken
ars

@ars: Ah evet, Andrej Bauer'un gönderisine bağlantı için teşekkürler. Evet, geç bağlamanın etkileri kesinlikle matematik tiplerimizin (daha kötüsü, Haskell kökenli) daha da beklenmedik ve şok edici bulmaya devam ettiğimiz bir şeydir. :-) Eminim kadarıyla Prof. Bauer olarak gidip bir tasarım hatası derim değilim ama olduğu tamamen bir düşünce yolu ile diğeri arasında geçiş yapmak insan programcılar için zor. (Ya da belki de bu benim yetersiz Python deneyimim.)
ShreevatsaR

26

Python en son sürümlerinde (> = 2.7) şunları yapabilirsiniz picklebir partial, ancak bir lambda:

>>> pickle.dumps(partial(int))
'cfunctools\npartial\np0\n(c__builtin__\nint\np1\ntp2\nRp3\n(g1\n(tNNtp4\nb.'
>>> pickle.dumps(lambda x: int(x))
Traceback (most recent call last):
  File "<ipython-input-11-e32d5a050739>", line 1, in <module>
    pickle.dumps(lambda x: int(x))
  File "/usr/lib/python2.7/pickle.py", line 1374, in dumps
    Pickler(file, protocol).dump(obj)
  File "/usr/lib/python2.7/pickle.py", line 224, in dump
    self.save(obj)
  File "/usr/lib/python2.7/pickle.py", line 286, in save
    f(self, obj) # Call unbound method with explicit self
  File "/usr/lib/python2.7/pickle.py", line 748, in save_global
    (obj, module, name))
PicklingError: Can't pickle <function <lambda> at 0x1729aa0>: it's not found as __main__.<lambda>

1
Ne yazık ki kısmi işlevler için turşu başarısız multiprocessing.Pool.map(). stackoverflow.com/a/3637905/195139
14'te

3
@wting Bu yazı 2010 partialyılından itibaren. Python 2.7'de alınabilir.
Fred Foo

23

Functools bir şekilde daha verimli mi?

Buna kısmen cevap olarak performansı test etmeye karar verdim. İşte benim örnek:

from functools import partial
import time, math

def make_lambda():
    x = 1.3
    return lambda: math.sin(x)

def make_partial():
    x = 1.3
    return partial(math.sin, x)

Iter = 10**7

start = time.clock()
for i in range(0, Iter):
    l = make_lambda()
stop = time.clock()
print('lambda creation time {}'.format(stop - start))

start = time.clock()
for i in range(0, Iter):
    l()
stop = time.clock()
print('lambda execution time {}'.format(stop - start))

start = time.clock()
for i in range(0, Iter):
    p = make_partial()
stop = time.clock()
print('partial creation time {}'.format(stop - start))

start = time.clock()
for i in range(0, Iter):
    p()
stop = time.clock()
print('partial execution time {}'.format(stop - start))

Python 3.3'te şunları verir:

lambda creation time 3.1743163756961392
lambda execution time 3.040552701787919
partial creation time 3.514482823352731
partial execution time 1.7113973411608114

Bu, kısmi oluşturma için biraz daha zamana, ancak yürütme için önemli ölçüde daha az zamana ihtiyaç duyduğu anlamına gelir. Bu, ars cevabında tartışılan erken ve geç bağlanmanın etkisi olabilir .


3
Daha da önemlisi, partialsaf Python'dan ziyade C ile yazılır, yani başka bir işlevi çağıran bir işlev oluşturmaktan daha verimli bir çağrılabilir üretebilir.
chepner

12

Alex'in bahsettiği ekstra işlevselliğin yanı sıra, functools'un başka bir avantajı. Kısmi ile başka bir yığın çerçeve oluşturmaktan (ve yok etmek) kaçınabilirsiniz.

Ne kısmi ne de lambdalar tarafından oluşturulan işlev varsayılan olarak docstring'lere sahip değildir (ancak herhangi bir nesne için doc dizesini ayarlayabilirsiniz __doc__).

Bu blogda daha fazla ayrıntı bulabilirsiniz: Python'da Kısmi İşlev Uygulaması


Hız avantajını test ettiyseniz, lambda üzerinde kısmi bir hız artışı beklenebilir mi?
Trilarion

1
Öğretinin miras alındığını söylediğinizde, hangi Python sürümüne atıfta bulunuyorsunuz? Python 2.7.15 ve Python 3.7.2'de miras alınmazlar. Bu iyi bir şeydir, çünkü orijinal doktrin kısmen uygulanan argümanlarla işlev için doğru değildir.
Ocak

Python 2.7 ( docs.python.org/2/library/functools.html#partial-objects ) için: " ad ve doküman özellikleri otomatik olarak oluşturulmaz". 3 için de aynıdır [5-7].
Yaroslav Nikitenko

Bağlantınızda bir hata var: log_info = kısmi (log_template, level = "info") - bu mümkün değil çünkü seviye örnekte bir anahtar kelime argümanı değil. Hem python 2 hem de 3 şunları söylüyor: "TypeError: log_template () 'level' argümanı için birden fazla değer aldı".
Yaroslav Nikitenko

Aslında, elle kısmi (f) oluşturdum ve doc alanına 'kısmi (func, * argümanlar, ** anahtar kelimeler) - verilen argümanların ve anahtar kelimelerin kısmi uygulaması \ n ile yeni işlev. \ N' (her ikisi de) python 2 ve 3 için).
Yaroslav Nikitenko

1

Üçüncü örnekte amacı en çabuk anlıyorum.

Lambdaları ayrıştırdığımda, doğrudan standart kütüphane tarafından sunulandan daha fazla karmaşıklık / gariplik bekliyorum.

Ayrıca, üçüncü örneğin tam imzasına bağlı olmayan tek örnek olduğunu göreceksiniz sum2; böylece biraz daha gevşek bir şekilde birleştirilir.


1
Hm, aslında tam tersi iknadayım, functools.partialaramayı ayrıştırmak çok daha uzun sürdü , oysa lambdalar apaçık ortada.
David Z
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.