Различия между functools.partial и аналогичной лямбдой?

В Python предположим, что у меня есть функция f, которую я хочу передать с некоторыми вторичными аргументами (предположим для простоты, что это только первый аргумент, который остается переменным).

В чем разница между этими двумя способами (если есть)?

# Assume secondary_args and secondary_kwargs have been defined

import functools

g1 = functools.partial(f, *secondary_args, **secondary_kwargs)
g2 = lambda x: f(x, *secondary_args, **secondary_kwargs)

Например, на странице для partial есть такая цитата:

partial объекты, определенные в классах, ведут себя как статические методы и не преобразуются в связанные методы во время поиска атрибутов экземпляра.

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


person ely    schedule 06.08.2012    source источник
comment
Я думаю, что мой вопрос сильно отличается от связанного, однако главный ответ на этот вопрос настолько подробен, что он также отвечает (по совпадению) на большую часть того, что я искал. Не буду жаловаться, если его закроют как дубликат.   -  person ely    schedule 06.08.2012
comment
Я согласен, что это не дубликат, я повторно открыл ваш вопрос. Для справки других, соответствующий вопрос находится здесь.   -  person wim    schedule 30.07.2014


Ответы (5)


  1. Лямбда-функция имеет тот же тип, что и стандартная функция, поэтому она будет вести себя как метод экземпляра.

  2. Объект partial в вашем примере можно вызвать так:

    g1(x, y, z)
    

    ведущий к этому вызову (неверный синтаксис Python, но идею вы поняли):

    f(*secondary_args, x, y, z, **secondary_kwargs)
    

    Лямбда принимает только один аргумент и использует другой порядок аргументов. (Конечно, оба эти различия можно преодолеть - я просто отвечаю, каковы различия между двумя версиями, которые вы указали.)

  3. Выполнение объекта partial немного быстрее, чем выполнение эквивалентного lambda.

person Sven Marnach    schedule 06.08.2012
comment
Вы профилировали объект partial по сравнению с lambda? - person orlp; 06.08.2012
comment
@nightcracker: Да, не сейчас, а несколько раз в прошлом. Объект partial не создает объект кода Python, поэтому он сохраняет полный фрейм стека Python. - person Sven Marnach; 06.08.2012
comment
@Sven Marnach: а что именно быстрее, код создания, код вызова или и то, и другое? - person orlp; 06.08.2012
comment
@nightcracker: В большинстве случаев имеет значение только время выполнения, так что это то, что я измерил. - person Sven Marnach; 06.08.2012

Резюме

Практические различия между lambda и functools.partial в общих случаях использования кажутся

  • functools.partial требуется импорт, lambda нет.
  • Определение функции для функций, созданных с помощью functools.partial, можно увидеть, просто распечатав созданную функцию. Функции, созданные с помощью lambda, следует проверять с помощью inspect.getsource().

Они оказались практически идентичными для lambda и functools.partial

  • Скорость
  • Tracebacks

Скорость (лямбда против functools.partial)

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

Похоже, что нет статистических доказательств разницы в скорости между lambda и functools.partial. Я проводил разные тесты с разным количеством повторений, каждый раз получая немного разные результаты; любой из трех подходов может быть самым быстрым. Скорости были идентичны с достоверностью 95% (2 сигма). Вот некоторые численные результаты *

# When functions are defined beforehand
In [1]: timeit -n 1000 -r 1000 f_partial(data)
23.6 µs ± 2.92 µs per loop (mean ± std. dev. of 1000 runs, 1000 loops each)

In [2]: timeit -n 1000 -r 1000 f_lambda(data)
22.6 µs ± 2.6 µs per loop (mean ± std. dev. of 1000 runs, 1000 loops each)

# When function is defined each time again
In [3]: timeit -n 1000 -r 1000 (lambda x: trim_mean(x, 0.1))(data)
22.6 µs ± 1.98 µs per loop (mean ± std. dev. of 1000 runs, 1000 loops each)

In [4]: timeit -n 1000 -r 1000 f_lambda = lambda x: trim_mean(x, 0.1); f_lambda(data)
23.7 µs ± 3.89 µs per loop (mean ± std. dev. of 1000 runs, 1000 loops each)

In [5]: timeit -n 1000 -r 1000 f_partial = partial(trim_mean, proportiontocut=0.1); f_partial(data)
24 µs ± 3.38 µs per loop (mean ± std. dev. of 1000 runs, 1000 loops each)

Tracebacks

Я также пробовал запускать f_lambda и f_partial, используя список со вставленным строковым элементом, и трассировки были равны (за исключением, конечно, самой первой записи). Так что никакой разницы нет.

Проверка исходного кода

  • Определение функции для функций, созданных с помощью functools.partial, можно увидеть, просто распечатав созданную функцию. Функции, созданные с помощью lambda, следует проверять с помощью inspect.getsource().
# Can be inspected with just printing the function
In [1]: f_partial
Out[1]: functools.partial(<function trim_mean at 0x000001463262D0D0>, proportiontocut=0.1)

In [2]: print(f_partial)
functools.partial(<function trim_mean at 0x000001463262D0D0>, proportiontocut=0.1)

# Lambda functions do not show the source directly
In [3]: f_lambda
Out[3]: <function __main__.<lambda>(x)>

# But you can use inspect.getsource()
In [4]: inspect.getsource(f_lambda)
Out[4]: 'f_lambda = lambda x: trim_mean(x, 0.1)\n'

# This throws a ValueError, though.
In [5]: inspect.getsource(f_partial)

Приложение

* Настройка, использованная в тестах

from functools import partial
from scipy.stats import trim_mean
import numpy as np
data = np.hstack((np.random.random(1000), np.random.random(50)*25000))

f_lambda = lambda x: trim_mean(x, 0.1)
f_partial = partial(trim_mean, proportiontocut=0.1)

Тестирование проводилось на 64-битной версии Python 3.7.3 (Windows 10).

person np8    schedule 10.07.2020
comment
lambda функции принимают только один позиционный аргумент. Только если так определено. Им можно поставить любую подпись (но без аннотации). - person Caagr98; 25.08.2020

частичные выражения не только примерно на 20% быстрее, чем эквивалентные лямбды, как уже было сказано, но и сохраняют прямую ссылку на то, к чему они относятся. В лямбдах эта функция «похоронена» внутри тела функции.

=> Если вам нужно решить только проблему отсрочки вычисления одной функции до тех пор, пока не будут известны все аргументы, используйте частичные. У вас будут гораздо лучшие методы интроспекции по сравнению с тем, чтобы скрыть вызовы в анонимных функциях, то есть лямбдах.

person Red Pill    schedule 02.07.2017

Я считаю, что метод класса применяется только к функциям, назначенным во время определения класса. Функции, назначенные позже, специально не обрабатываются.

Помимо этого, я лично предпочитаю лямбды, поскольку они более распространены и, следовательно, упрощают понимание кода.

class Foo(object):
    def __init__(self, base):
        self.int = lambda x:int(x, base)

print Foo(4).int('11')
person Antimony    schedule 06.08.2012
comment
I'd personally favor lambdas since they're more common and hence make the code easier to understand. Я никогда раньше не слышал ни одного из этих заявлений. - person phant0m; 06.08.2012
comment
@ phant0m В большинстве утверждений о лямбдах Python, которые я слышал, было слово «сломанный». ;-) - person Chris Wesseling; 21.11.2013
comment
@Chris functools.partial чище, но я думаю, что лямбды легче понять, если у вас нет проблем с областью видимости. Во-первых, лямбды означают, что вам не нужно запоминать точный порядок аргументов, который принимает functools.partial. - person Antimony; 21.11.2013

Да, lambda от этого "пострадает". partial не имеет этой проблемы, потому что это объект с перегруженным оператором вызова, а не реальная функция.

Но использование такой лямбды в определении класса - это просто неправильное использование.

person orlp    schedule 06.08.2012