Подтвердить что ты не робот

"лямбда" против "operator.attrgetter('xxx')" как функция ключа сортировки

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

Если вам нужно изменить seq.sort(lambda x,y: cmp(x.xxx, y.xxx)), что предпочтительнее:

seq.sort(key=operator.attrgetter('xxx'))

или

seq.sort(key=lambda a:a.xxx)

Мне также будет интересен комментарий о преимуществах внесения изменений в существующий код, который работает.

4b9b3361

Ответ 1

При выборе чисто между attrgetter('attributename') и lambda o: o.attributename в качестве ключа сортировки, тогда использование attrgetter() является более быстрым вариантом.

Помните, что функция key применяется только один раз к каждому элементу в списке перед сортировкой, поэтому для сравнения двух мы можем использовать их непосредственно во временном испытании:

>>> from timeit import Timer
>>> from random import randint
>>> from dataclasses import dataclass, field
>>> @dataclass
... class Foo:
...     bar: int = field(default_factory=lambda: randint(1, 10**6))
...
>>> testdata = [Foo() for _ in range(1000)]
>>> def test_function(objects, key):
...     [key(o) for o in objects]
...
>>> stmt = 't(testdata, key)'
>>> setup = 'from __main__ import test_function as t, testdata; '
>>> tests = {
...     'lambda': setup + 'key=lambda o: o.bar',
...     'attrgetter': setup + 'from operator import attrgetter; key=attrgetter("bar")'
... }
>>> for name, tsetup in tests.items():
...     count, total = Timer(stmt, tsetup).autorange()
...     print(f"{name:>10}: {total / count * 10 ** 6:7.3f} microseconds ({count} repetitions)")
...
    lambda: 130.495 microseconds (2000 repetitions)
attrgetter:  92.850 microseconds (5000 repetitions)

Таким образом, применение attrgetter('bar') 1000 раз примерно на 40 мкс быстрее, чем lambda. Это потому, что вызов функции Python имеет определенное количество накладных расходов, больше, чем вызов нативной функции, например, созданной attrgetter().

Это преимущество в скорости также приводит к более быстрой сортировке:

>>> def test_function(objects, key):
...     sorted(objects, key=key)
...
>>> for name, tsetup in tests.items():
...     count, total = Timer(stmt, tsetup).autorange()
...     print(f"{name:>10}: {total / count * 10 ** 6:7.3f} microseconds ({count} repetitions)")
...
    lambda: 218.715 microseconds (1000 repetitions)
attrgetter: 169.064 microseconds (2000 repetitions)

Ответ 2

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

Что касается двух способов написания простой функции key=, целью дизайна было сделать operator.attrgetter быстрее, будучи более специализированным, но по крайней мере в текущих версиях Python нет заметной разницы в скорости. В этом случае, для этой особой ситуации я бы рекомендовал lambda, просто потому, что он более краткий и общий (и я обычно не лямбда-любовник, заметьте!).

Ответ 3

Как указывалось в предыдущих комментариях, attrgetter немного быстрее, но для многих ситуаций разница незначительна (~ микросекунды).

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

Еще одно предостережение: ваша IDE должна иметь возможность сообщать опечатку в имени attr при использовании lambda, в отличие от использования attrgetter.

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

Ответ 4

Это на самом деле ваше решение.

Я бы предпочел использовать attrgetter потому что lambda не очень чистая, и намного намного быстрее, посмотрите на время:

>>> timeit.timeit(lambda: seq.sort(key=operator.attrgetter('xxx')),number=10)
0.00013137529401774373
>>> timeit.timeit(lambda: seq.sort(key=lambda a:a.xxx),number=10)
5.952943013198819e-05
>>> 

Так что это очень ясно, attrgetter намного быстрее, без лямбды, намного чище.