web-dev-qa-db-fra.com

Manière pythonique de trier la liste des syntagmes nommés par nom de champ

Je veux trier une liste de tuples nommés sans avoir à me souvenir de l'index du nom de champ. Ma solution semble plutôt délicate et espérait que quelqu'un disposerait d'une solution plus élégante.

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'))))

Merci, Nick

41
Nick
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)]

Trier la liste par nom

sorted(seq, key=attrgetter('name'))

Trier la liste par âge

sorted(seq, key=attrgetter('age'))
64
jamylak
sorted(seq, key=lambda x: x.name)
sorted(seq, key=lambda x: x.age)
45
clyfish

J'ai testé les deux alternatives données ici pour la vitesse, car @zenpoy était soucieux de ses performances.

Script de test:

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))

Résultats:

attrgetter_test_name 44.26793992166096
attrgetter_test_age 31.98247099677627
lambda_test_name 47.97959511074551
lambda_test_age 35.69356267603864

L'utilisation de lambda était en effet plus lente. Jusqu'à 10% plus lent.

MODIFIER:

Des tests supplémentaires montrent les résultats lors du tri utilisant plusieurs attributs. Ajout des deux cas de test suivants avec la même configuration:

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))

Résultats:

attrgetter_test_both 92.80101586919373
lambda_test_both 96.85089983147456

Lambda continue de sous-performer, mais moins. Maintenant environ 5% plus lent.

Les tests sont effectués sur Python 3.6.0.

5

puisque personne n'a mentionné utiliser itemgetter (), voici comment vous utilisez 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)))
0
sir.Suhrab

C'est peut-être un peu trop "magique" pour certains, mais je suis partial:

# sort list by name
print(sorted(seq, key=Person.name.fget))
0
Sumudu Fernando