2010-07-15 2 views
138

Частичное применение прохладно. Какая функциональность предлагает functools.partial, что вы не можете пройти через лямбды?Python: Почему нужен functools.partial?

>>> 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 

Действительно ли functools как-то более эффективно или удобочитаемо?

ответ

1

Я понимаю намерение быстрее всего в третьем примере.

Когда я разбираю лямбды, я ожидаю больше сложностей/странностей, чем предлагаемая стандартной библиотекой напрямую.

Кроме того, вы заметите, что третий пример является единственным, который не зависит от полной подписи sum2; что делает его немного более слабо связанным.

+1

Хм, я нахожусь в обратном убеждении, я потратил намного больше времени, чтобы разобрать «functools».частичный' вызов, тогда как лямбды самоочевидны. –

57

Ну, вот пример, который показывает разницу:

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 расширить на "ограничения лямбда" и затворами питона:

+1

Хороший пример. Для меня это кажется скорее «ошибкой» с лямбдой, но я понимаю, что другие могут не согласиться. (Что-то подобное происходит с замыканиями, определенными в цикле, как реализовано на нескольких языках программирования.) – ShreevatsaR

+23

Исправление этой «ранней связи с поздним связыванием» заключается в том, чтобы явно использовать раннее связывание, когда вы этого хотите, посредством 'lambda y, n = n: ... '. Позднее связывание (имен, появляющихся _only_ в теле функции, а не в его 'def' или эквивалентном' lambda'), является чем-то **, но ** ошибкой, как я подробно рассказывал в предыдущих ответах SO в прошлом: вы раннее связывание явно, когда это то, что вы хотите, используйте умолчание по умолчанию, когда * that * - это то, что вы хотите, и это _exactly_ правильный выбор дизайна, учитывая контекст остальной части дизайна Python. –

+1

Да, это хороший пример. –

212

Какая функциональность functools.partial предлагают, чтобы вы не могли пройти через лямбды?

Не так много в плане дополнительной функциональности (но, смотрите далее) - и, читаемость в глазах смотрящего.
Большинство людей, знакомых с функциональными языками программирования (в частности, в семьях Lisp/Scheme), похоже, как lambda просто отлично - я говорю «большинство», определенно не все, потому что мы с Guido, безусловно, среди тех «знакомы с» (и т. д.), но думают о lambda как аномалия в области зрения на Python ...
Он был раскаивающимся в том, что он принял его на Python, тогда как планировал удалить его из Python 3, как один из «сбоев Python».
Я полностью поддержал его в этом. (Я люблю lambdaв схеме ... в то время как его ограничение в Python и странным образом просто не вписывается в с остальной частью языка, делает мою кожу ползать).

Не так, однако, для полчищ lambda любителей - которые организовали один из самых близких вещей к восстанию когда-либо видели в истории Пайтона, пока Guido не отошла и решил оставить lambda в
Несколько возможных дополнений functools. (чтобы сделать функции, возвращающие константы, идентичность и т. д.) не произошло (чтобы избежать явного дублирования функций lambda), хотя partial, конечно, остался (это не всего дублирования, и это не является глазом).

Помните, что корпус lambda ограничен ссылкой , поэтому у него есть ограничения. Например...:

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

functools.partial «s возвращается функция украшен атрибутами полезных для самоанализа - функция это упаковка, а также то, что позиционные и именованные аргументы он фиксирует в нем. Кроме того, названные аргументы могут быть перекрыты обратно («закреплением» является, скорее, в некотором смысле, установка по умолчанию):

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

Итак, как вы видите, это definely не так просто, как это lambda s: int(s, base=2) ! -)

Да, вы могли искажать свою лямбду, чтобы дать вам некоторые из этого - например, по ключевому слову-переопределение,

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

но я дорого надежда что даже самый горячий lambda -lover не считает этим ужасом более читаемым, чем partial звоните! -). Часть атрибута «еще больше сложна» из-за ограничения «тело - единственное выражение» Python lambda (плюс тот факт, что присваивание никогда не может быть частью выражения Python) ... вы в конечном итоге «приписываете назначения в выражении "растяжение списка понимания далеко за ее пределы дизайна ...:

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

Теперь смешайте с именем-аргументы overridability, а также установку трех атрибутов, в одно выражение, и скажите мне, насколько читаемым что будет ...! -)

+0

Да, я бы сказал, что дополнительная функциональность «functools.partial», которую вы упомянули, делает ее выше лямбда. Возможно, это тема другого поста, но что это такое на уровне дизайна, который вас так беспокоит «лямбда»? –

+11

@ Розарх, как я уже сказал: во-первых, это ограничения (Python резко отличает выражения и утверждения - вы не можете сделать или не можете делать _sensibly_ в пределах одного выражения, и это то, что тело лямбды _is_); во-вторых, его совершенно странный синтаксический сахар. Если бы я мог вернуться во времени и изменить одну вещь в Python, это были бы абсурдные, бессмысленные, гласные слова «def» и «lambda»: сделайте их как 'function' (один выбор имени Javascript получил _really_ right), и на менее 1/3 моих возражений исчезнет! -). Как я уже сказал, у меня нет возражений против лямбда _in Lisp _...! -) –

+1

@Alex Martelli, почему Guido установил такое ограничение для лямбда: «тело - одно выражение»? Тело лямбды C# может быть любым, что действует в теле функции. Почему Guido просто не снимает ограничение на лямбда python? –

10

Помимо дополнительной функциональности, которую Алекс упомянул, еще одним преимуществом functools.partial является скорость. С частичным вы можете избежать создания (и разрушения) другого фрейма стека.

Функция порождено частичным наследует строку документации от исходной функции в то время как лямбда нет строки документации по умолчанию (хотя вы можете установить строку документации для любых объектов через __doc__)

Вы можете найти более подробную информацию в этом блоге: Partial Function Application in Python

+0

Если вы протестировали преимущество скорости, то можно ли ожидать улучшения скорости частичного лямбда? – Trilarion

20

В последних версиях Python (> = 2,7), вы можете picklepartial, но не 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

К сожалению, частичные функции не могут размножаться для 'multiprocessing.Pool.map()'. http://stackoverflow.com/a/3637905/195139 – wting

+1

@wting Это сообщение с 2010 года. 'partial' является макетирующим в Python 2.7. –

15

Является ли functools как-то более эффективным ..?

В качестве частичного ответа на это я решил проверить работу. Вот мой пример:

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 дает:

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

Это означает, что парциальное требуется немного больше времени для создания, но значительно меньше времени для выполнения. Это может быть следствием раннего и позднего связывания, которые обсуждаются в ответе от ars.

+1

Что еще более важно, 'partial' написан на языке C, а не на чистом Python, что означает, что он может создать более эффективный вызов, чем просто создать функцию, которая вызывает другую функцию. – chepner

Смежные вопросы