Различия между 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 объекты, определенные в классах, ведут себя как статические методы и не преобразуются в связанные методы во время поиска атрибутов экземпляра.

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

6 ответов

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

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

    g1(x, y, z)
    

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

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

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

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

Резюме

В практических различиях между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).

Здесь упущен самый важный момент -lambdaесть ссылки на входные переменные, ноpartitionсделать копию аргументов во время создания:

      >>> for k,v in {"1": "2", "3": "4"}.items():
...     funcs.append(lambda: print(f'{k}: {v}'))
...
>>> print(funcs)
[<function <lambda> at 0x106db71c0>, <function <lambda> at 0x10747a3b0>]
>>> for f in funcs:
...     f()
...
3: 4  # result are indentical
3: 4
      >>> import functools
>>> funcs = []
>>> for k,v in {"1": "2", "3": "4"}.items():
...     funcs.append(functools.partial(print, f'{k}: {v}'))
...
>>> print(funcs)
[functools.partial(<built-in function print>, '1: 2'), functools.partial(<built-in function print>, '3: 4')]
>>>
>>> for f in funcs:
...     f()
...
1: 2  # result differs
3: 4

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

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

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

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

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

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

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

print Foo(4).int('11')
Другие вопросы по тегам