75

我想对命名元组列表进行排序,而不必记住字段名的索引。我的解决方案似乎很尴尬,并希望有人能有一个更优雅的解决方案。

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

谢谢,尼克

4

5 回答 5

96
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'))
于 2012-08-23T08:48:23.720 回答
66
sorted(seq, key=lambda x: x.name)
sorted(seq, key=lambda x: x.age)
于 2012-08-23T08:50:12.423 回答
14

我测试了这里给出的两种替代方案的速度,因为@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 上完成。

于 2017-05-27T17:28:15.880 回答
4

这对某些人来说可能有点太“神奇”,但我偏爱:

# 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

于 2019-01-10T07:39:48.993 回答
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)))
于 2018-03-10T19:18:56.763 回答