Python: Почему необходим functools.partial? - PullRequest
173 голосов
/ 15 июля 2010

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

Ответы [ 6 ]

241 голосов
/ 15 июля 2010

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

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

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

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

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

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

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

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

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

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

69 голосов
/ 15 июля 2010

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

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:

25 голосов
/ 09 октября 2013

В последних версиях 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's not found as __main__.<lambda>
21 голосов
/ 09 июля 2014

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

11 голосов
/ 05 сентября 2013

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

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

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

1 голос
/ 15 июля 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...