"лямбда" против "operator.attrgetter ('xxx')" как функция ключа сортировки в Python - PullRequest
28 голосов
/ 24 апреля 2010

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

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

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

или

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

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

Ответы [ 2 ]

21 голосов
/ 24 апреля 2010

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

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

10 голосов
/ 06 января 2019

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

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

>>> 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...