Python: Почему необходим functools.partial?

Частичное приложение классно. Какая функциональность 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

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

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

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

Помните, что тело lambda ограничено выражением, поэтому оно имеет ограничения. Например...:

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

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

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

Итак, как вы видите, он определенно не так упрощен, как 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]

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

Ответ 2

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

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

Эти сообщения Ивана Мура раскрывают "ограничения лямбда" и замыкания в python:

Ответ 3

В последних версиях Python ( >= 2.7) вы можете pickle a partial, но не 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 not found as __main__.<lambda>

Ответ 4

Является 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.

Ответ 5

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

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

Вы можете найти более подробную информацию в этом блоге: Приложение с частичной функцией в Python

Ответ 6

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

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

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