【问题标题】:Pythonic way to sorting list of namedtuples by field namePythonic 按字段名称对命名元组列表进行排序的方法
【发布时间】: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 的解决方案也有效?
  • 我并没有尝试做任何动态的事情,所以这两种解决方案都能完美运行。

标签: python sorting namedtuple field-names


【解决方案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 的表现要好得多,而 lambdas 通常被认为不优雅
    • 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 中有所改变。不久将添加对答案的编辑。
      猜你喜欢
      • 1970-01-01
      • 2015-01-28
      • 1970-01-01
      • 1970-01-01
      • 2022-01-03
      • 2011-10-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多