Pythonic способ сортировки списка namedtuples по имени поля
Я хочу отсортировать список именованных кортежей, не запомнив индекс имени поля. Мое решение кажется довольно неудобным и надеялось, что у кого-то будет более элегантное решение.
from operator import itemgetter
from collections import namedtuple
Person = namedtuple('Person', 'name age score')
seq = [
Person(name='nick', age=23, score=100),
Person(name='bob', age=25, score=200),
]
# sort list by name
print(sorted(seq, key=itemgetter(Person._fields.index('name'))))
# sort list by age
print(sorted(seq, key=itemgetter(Person._fields.index('age'))))
Спасибо,
Ник
Ответы
Ответ 1
from operator import attrgetter
from collections import namedtuple
Person = namedtuple('Person', 'name age score')
seq = [Person(name='nick', age=23, score=100),
Person(name='bob', age=25, score=200)]
Сортировать список по названию
sorted(seq, key=attrgetter('name'))
Сортировка по возрасту
sorted(seq, key=attrgetter('age'))
Ответ 2
sorted(seq, key=lambda x: x.name)
sorted(seq, key=lambda x: x.age)
Ответ 3
Я тестировал две альтернативы, приведенные здесь для скорости, поскольку @zenpoy был обеспокоен производительностью.
Тестирование script:
import random
from collections import namedtuple
from timeit import timeit
from operator import attrgetter
runs = 10000
size = 10000
random.seed = 42
Person = namedtuple('Person', 'name,age')
seq = [Person(str(random.randint(0, 10 ** 10)), random.randint(0, 100)) for _ in range(size)]
def attrgetter_test_name():
return sorted(seq.copy(), key=attrgetter('name'))
def attrgetter_test_age():
return sorted(seq.copy(), key=attrgetter('age'))
def lambda_test_name():
return sorted(seq.copy(), key=lambda x: x.name)
def lambda_test_age():
return sorted(seq.copy(), key=lambda x: x.age)
print('attrgetter_test_name', timeit(stmt=attrgetter_test_name, number=runs))
print('attrgetter_test_age', timeit(stmt=attrgetter_test_age, number=runs))
print('lambda_test_name', timeit(stmt=lambda_test_name, number=runs))
print('lambda_test_age', timeit(stmt=lambda_test_age, number=runs))
Результаты:
attrgetter_test_name 44.26793992166096
attrgetter_test_age 31.98247099677627
lambda_test_name 47.97959511074551
lambda_test_age 35.69356267603864
Использование лямбда было действительно медленнее. До 10% медленнее.
ИЗМЕНИТЬ
Дальнейшее тестирование показывает результаты при сортировке с использованием нескольких атрибутов. Добавлены следующие два тестовых примера с одной и той же настройкой:
def attrgetter_test_both():
return sorted(seq.copy(), key=attrgetter('age', 'name'))
def lambda_test_both():
return sorted(seq.copy(), key=lambda x: (x.age, x.name))
print('attrgetter_test_both', timeit(stmt=attrgetter_test_both, number=runs))
print('lambda_test_both', timeit(stmt=lambda_test_both, number=runs))
Результаты:
attrgetter_test_both 92.80101586919373
lambda_test_both 96.85089983147456
Лямбда все еще хуже, но тем более. Теперь примерно на 5% медленнее.
Тестирование выполняется на Python 3.6.0.
Ответ 4
так как никто не упоминал об использовании itemgetter(), вот как вы это делаете с помощью itemgetter().
from operator import itemgetter
from collections import namedtuple
Person = namedtuple('Person', 'name age score')
seq = [
Person(name='nick', age=23, score=100),
Person(name='bob', age=25, score=200),
]
# sort list by name
print(sorted(seq, key=itemgetter(0)))
# sort list by age
print(sorted(seq, key=itemgetter(1)))
Ответ 5
Это может быть слишком "волшебным" для некоторых, но я неравнодушен к:
# sort list by name
print(sorted(seq, key=Person.name.fget))