如何根据对象的属性对对象列表进行排序?


803

我有一个Python对象列表,我想按对象本身的属性对其进行排序。该列表如下所示:

>>> ut
[<Tag: 128>, <Tag: 2008>, <Tag: <>, <Tag: actionscript>, <Tag: addresses>,
 <Tag: aes>, <Tag: ajax> ...]

每个对象都有一个计数:

>>> ut[1].count
1L

我需要按递减计数对列表进行排序。

我已经看到了几种方法,但是我正在寻找Python的最佳实践。



1
Sorting HOW TO对于那些正在寻找有关Python排序的更多信息的人。
Jeyekomon

1
除了operator.attrgetter('attribute_name')之外,您还可以使用仿函数作为键,例如object_list.sort(key = my_sorting_functor('my_key'))),有意地将实现留在了外面。
vijay shanker

Answers:


1312
# To sort the list in place...
ut.sort(key=lambda x: x.count, reverse=True)

# To return a new list, use the sorted() built-in function...
newlist = sorted(ut, key=lambda x: x.count, reverse=True)

有关按键排序的更多信息。


1
没问题。顺便说一句,如果muhuk是正确的,并且它是Django对象的列表,那么您应该考虑他的解决方案。但是,对于排序对象的一般情况,我的解决方案可能是最佳实践。
三联画

43
在大型列表上,使用operator.attrgetter('count')作为键将获得更好的性能。这只是此答案中lambda函数的优化形式(较低级别)。
David Eyk

4
感谢您的出色回答。如果它是字典列表,并且'count'是其键之一,则需要如下更改:ut.sort(key = lambda x:x ['count'],reverse = True)
dganesh2002

我想应该进行以下更新:如果需要按多个字段进行排序,则可以通过连续调用sort()来实现,因为python使用的是稳定的排序算法。
zzz777

86

可以使用最快的方法,尤其是在您的列表中有很多记录的情况下operator.attrgetter("count")。但是,它可以在预操作者版本的Python上运行,因此具有后备机制会很好。然后,您可能需要执行以下操作:

try: import operator
except ImportError: keyfun= lambda x: x.count # use a lambda if no operator module
else: keyfun= operator.attrgetter("count") # use operator since it's faster than lambda

ut.sort(key=keyfun, reverse=True) # sort in-place

7
在这里,我将使用变量名“ keyfun”而不是“ cmpfun”以避免混淆。sort()方法也确实通过cmp =参数接受比较函数。
akaihola

如果对象具有动态添加的属性,这似乎不起作用(如果您self.__dict__ = {'some':'dict'}__init__方法之后完成操作)。不过,我不知道为什么会有所不同。
tutuca

@tutuca:我从未替换过实例__dict__。注意,“具有动态添加的属性的对象”和“设置对象的__dict__属性”几乎是正交的概念。我说这是因为您的评论似乎暗示着设置__dict__属性是动态添加属性的必要条件。
tzot

@tzot:我正在看这个:github.com/stochastic-technologies/goatfish/blob/master/…并在这里使用该迭代器:github.com/TallerTechnologies/dishey/blob/master/app.py#L28引发属性错误。也许是因为python3,但仍然...
tutuca

1
@tzot:如果我了解的用法operator.attrgetter,则可以提供具有任何属性名称的函数并返回已排序的集合。
IAbstract

64

读者应注意,key =方法:

ut.sort(key=lambda x: x.count, reverse=True)

比向对象添加丰富的比较运算符快许多倍。我很惊讶地阅读了这篇文章(“ Python in a Nutshell”的第485页)。您可以通过在这个小程序上运行测试来确认这一点:

#!/usr/bin/env python
import random

class C:
    def __init__(self,count):
        self.count = count

    def __cmp__(self,other):
        return cmp(self.count,other.count)

longList = [C(random.random()) for i in xrange(1000000)] #about 6.1 secs
longList2 = longList[:]

longList.sort() #about 52 - 6.1 = 46 secs
longList2.sort(key = lambda c: c.count) #about 9 - 6.1 = 3 secs

我的非常少的测试表明,第一种方法的运行速度要慢10倍以上,但书中说,一般而言,它仅慢5倍左右。他们说的原因是由于python(timsort)中使用了高度优化的排序算法。

仍然,.sort(lambda)比普通的旧.sort()快是很奇怪的。我希望他们能解决这个问题。


1
定义__cmp__等效于调用.sort(cmp=lambda),而不是.sort(key=lambda),因此一点都不奇怪。
tzot

@tzot完全正确。第一类必须一次又一次地比较对象。第二种排序仅访问每个对象一次以提取其计数值,然后执行高度优化的简单数字排序。比较公平的比较是longList2.sort(cmp = cmp)。我尝试了一下,其效果几乎与相同.sort()。(另外:请注意,“ cmp”排序参数已在Python 3中删除。)
Bryan Roach,

43

面向对象的方法

最好将对象排序逻辑(如果适用)设置为类的属性,而不是在每个实例中都要求进行排序。

这样可以确保一致性,并且不需要样板代码。

至少,您应该指定__eq____lt__操作此功能。然后使用sorted(list_of_objects)

class Card(object):

    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __eq__(self, other):
        return self.rank == other.rank and self.suit == other.suit

    def __lt__(self, other):
        return self.rank < other.rank

hand = [Card(10, 'H'), Card(2, 'h'), Card(12, 'h'), Card(13, 'h'), Card(14, 'h')]
hand_order = [c.rank for c in hand]  # [10, 2, 12, 13, 14]

hand_sorted = sorted(hand)
hand_sorted_order = [c.rank for c in hand_sorted]  # [2, 10, 12, 13, 14]

1
那就是我想要的!你能带我们去一些文件,关于为什么详解__eq____lt__是最低实施要求?
FriendFX

1
@FriendFX,我相信这是暗示的这个•The sort routines are guaranteed to use __lt__() when making comparisons between two objects...
JPP

2
@FriendFX:请参阅portingguide.readthedocs.io/en/latest/comparisons.html进行比较和排序
Cornel Masson

37
from operator import attrgetter
ut.sort(key = attrgetter('count'), reverse = True)

16

它看起来很像Django ORM模型实例的列表。

为什么不对这样的查询进行排序:

ut = Tag.objects.order_by('-count')

它是,但是使用django-tagging,所以我使用内置的方法来按特定用途查询特定用途的标签集,例如:Tag.objects.usage_for_queryset(QuerySet,counts = True)
Nick Sergeant

11

将丰富的比较运算符添加到对象类,然后使用列表的sort()方法。
参见python中的丰富比较


更新:尽管此方法可行,但我认为Triptych的解决方案更简单,因此更适合您的情况。


3

如果要排序的属性property,则可以避免导入,operator.attrgetter而可以使用属性的fget方法。

例如,对于Circle具有属性的类,radius我们可以circles按如下所示对半径列表进行排序:

result = sorted(circles, key=Circle.radius.fget)

这不是最知名的功能,但通常使我免于导入的麻烦。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.