Pythonic 按字段名称对命名元组列表进行排序的方法
Posted
技术标签:
【中文标题】Pythonic 按字段名称对命名元组列表进行排序的方法【英文标题】:Pythonic way to sorting list of namedtuples by field name 【发布时间】:2012-08-18 17:58:43 【问题描述】:我想对命名元组列表进行排序,而不必记住字段名的索引。我的解决方案似乎很尴尬,希望有人能提供更优雅的解决方案。
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'))))
谢谢, 尼克
【问题讨论】:
字段名称是始终作为字符串给出还是@clyfish 的解决方案也有效? 我并没有尝试做任何动态的事情,所以这两种解决方案都能完美运行。 【参考方案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)
【讨论】:
我认为这比使用attrgetter
更优雅
我更喜欢 attrgetter,但这只是品味。如果我要让字段动态排序,还有一个优点。然后我可以传递字符串。
@zenpoy 请记住,attrgetter
的表现要好得多,而 lambda
s 通常被认为不优雅
和sorted(seq, key=lambda x: [x.age, x.name])
按多个属性排序【参考方案3】:
我测试了这里给出的两个替代方案的速度,因为@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 上完成。
【讨论】:
感谢多属性排序案例:)【参考方案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))
编辑:这假定namedtuple
使用内置的property()
来实现访问器,因为它利用了此类属性(see documentation)上的fget
属性。在某些实现中这可能仍然是正确的,但似乎 CPython 不再这样做,我认为这与 https://bugs.python.org/issue32492 中引用的优化工作有关(所以,从 3.8 开始)。这种脆弱性就是我提到的“魔法”的代价; namedtuple
当然不承诺使用property()
。
写Person.name.__get__
更好(在实现更改之前和之后工作)但可能不值得奥秘而不是更清楚地写成lambda p: p.name
【讨论】:
您能否在答案中添加一些上下文?我试过了,但得到 AttributeError: '_collections._tuplegetter' object has no attribute 'fget'。 当然。这种方法曾经有效,但它依赖于 namedtuple 的特定实现细节。这似乎在bugs.python.org/issue32492 中有所改变。不久将添加对答案的编辑。以上是关于Pythonic 按字段名称对命名元组列表进行排序的方法的主要内容,如果未能解决你的问题,请参考以下文章