我想对命名元组列表进行排序,而不必记住字段名的索引。我的解决方案似乎相当尴尬,希望有人能有一个更优雅的解决方案。
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'))))
谢谢,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)]
按名称排序列表
sorted(seq, key=attrgetter('name'))
按年龄排序列表
sorted(seq, key=attrgetter('age'))
sorted(seq, key=lambda x: x.name)
sorted(seq, key=lambda x: x.age)
我测试了这里给出的两种速度选择,因为@zenpoy关心性能。
测试脚本:
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
使用lambda确实较慢。速度慢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
Lambda仍然表现不佳,但没有那么糟糕。现在慢了大约5%。
测试是在Python 3.6.0上完成的。
由于没有人提到使用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)))
这对一些人来说可能有点太"神奇"了,但我偏爱:
# sort list by name
print(sorted(seq, key=Person.name.fget))
编辑:这假设namedtuple
使用内置的property()
来实现访问器,因为它利用了此类属性上的fget
属性(请参阅文档)。在某些实现中可能仍然是这样,但CPython似乎不再这样做了,我认为这与中引用的优化工作有关https://bugs.python.org/issue32492(因此,自3.8起);魔术;我提到;namedtuple
当然不会承诺使用property()
。
编写Person.name.__get__
更好(在实现更改之前和之后有效),但与将其更简单地编写为lambda p: p.name
相比,可能不值得晦涩难懂