文章来自《Python cookbook》.

翻译仅仅是为了个人学习,其它商业版权纠纷与此无关!

-- Zoom.Quiet [2004-08-11 17:01:13]

通过元素或者属性来排序

2.8 Sorting by Item or by Attribute 通过元素或者属性来排序

Credit: Matthew Wood

问题 Problem

You need to sort a list of (x, y) coordinates by item y, or, more generally, sort a list of objects by any attribute or item.

你需要通过元素y来排序与之相配的(x,y)的列表,或者,更一般地,通过任何属性或者元素来排序对象的列表

解决 Solution

You might first think of something like the following class, based on the simple but slow approach of passing a comparison function to the sort method:

你可能首先考虑象下面类的一些东西,基于简单但是缓慢的途径【传递一个比较函数到sort方法】

   1 class Sorter:
   2     # Notice how __compare is dependent on self.__whichindex
   3     def __compare(self, x, y):
   4         return cmp(x[self.__whichindex], y[self.__whichindex])
   5 
   6     # Pass the sort function the __compare function defined above
   7     def __call__(self, data, whichindex = None):
   8         if whichindex is None :
   9             data.sort(  )
  10         else :
  11             self.__whichindex = whichindex
  12             data.sort(self.__compare)
  13         return data               # handy for inlining, and low-cost

The trick is to use a bound method that accesses instance state as the comparison function to determine which item (or attribute, but this version deals only with items) to fetch. Unfortunately, this makes the approach nonreentrant and not thread-safe.

窍门是去使用一个bound方法,它访问实例状态来作为比较函数去决定那个元素(或者属性,但是这个版本仅仅处理元素)被取得。不幸的是,这使得方法不可重入并且不是线程安全的。

Thanks to the faster, more robust, and more flexible DSU idiom, it's not hard to make a more general version that allows attributes as well as items, guarantees a stable sort, is both reentrant and thread-safe, and is speedier to boot:

考虑到快速,更自然、优雅,并且更灵活的DSU惯用方法。

去制作一个更普遍的版本【它允许属性也允许元素,保证一个稳定的排序,即是可重入的又是线程安全的,并且快速启动的】不是很困难的

   1 class Sorter:
   2     def _helper(self, data, aux, inplace):
   3         aux.sort(  )
   4         result = [data[i] for junk, i in aux]
   5         if inplace: data[:] = result
   6         return result
   7 
   8     def byItem(self, data, itemindex=None, inplace=1):
   9         if itemindex is None:
  10             if inplace:
  11                 data.sort(  )
  12                 result = data
  13             else:
  14                 result = data[:]
  15                 result.sort(  )
  16             return result
  17         else:
  18             aux = [(d[i][itemindex], i) for i in range(len(data))]
  19             return self._helper(data, aux, inplace)
  20 
  21     # a couple of handy synonyms
  22     sort = byItem
  23     __call__ = byItem
  24 
  25     def byAttribute(self, data, attributename, inplace=1):
  26         aux = [(getattr(d[i],attributename),i) for i in range(len(data))]
  27         return self._helper(data, aux, inplace)

Of course, since the second version doesn't use its "classhood" in any way, making it a class is somewhat peculiar. It would be far more Pythonic, and clearer, to use a module with free-standing functions, rather than an artificial class with instances that have no state.

当然,因为第二个版本不使用它的任何方式的classshood,使它的类有些奇特。它更pythonic,更清晰的去使用一个具有自立的函数。而不是一个据用没有状态的实例的人造类。

讨论 Discussion

How do you efficiently sort a list of (x, y) coordinates by y? More generally, how do you sort a list of dictionaries, lists, or class instances by a particular item or attribute? I hate not being able to sort by any item or attribute, so I wrote an auxiliary class to do it for me.

你怎样通过y来有效的排序相匹配的(x,y)的类表?更普遍的是,你怎样通过一个特殊的元素或者属性来排序字典的列表,列表的列表或者类实例的列表?我憎恨不能通过任何元素或者属性来排序。所以我写了一个辅助的类去为我做它。

The DSU idiom is much faster than passing sort a comparison function, as discussed in other recipes. The second version of Sorter in this recipe uses DSU because of this, as well as auxiliary flexibility advantages. This second version gets no benefit from being a class rather than just a couple of functions, but casting it as a class makes it drop-in compatible with the first, inferior version, which did use some state as a trick (losing reentrancy and thread-safety in the process).

DSU惯例是比传递一个比较函数更快,就象在其他配方里讨论的那样。在这个配方第二个排序的版本使用DSU就是因为这个,辅助的弹性的好处。这个第二个版本没有从一个类而不是仅仅一对函数中得到好处。但是把它转换作为一个类使得它向下兼容第一个,低级的版本。它使用一些状态作为一个诀窍(在进程里失去了可重入性和线程安全)

Here is some example code (note that it instantiates the Sorter class only once, another hint that it is not at all an optimal architecture to wrap this functionality as a class): 这里是一些例子代码(注意它实例化一个Sorter类仅仅一次,其他的提示是它根本不是一个包装这个功能作为一个类的最佳架构)

   1 sort = Sorter(  )
   2 
   3 if __name__ == '__main__' :
   4       list = [(1, 2), (4, 8), (0, 3)]
   5       dict = [{'a': 3, 'b': 4}, {'a': 5, 'b': 2}, {'a': 0, 'b': 0},
   6           {'a': 9, 'b': 9}]
   7       dumb = [1, 4, 6, 7, 2, 5, 9, 2, 4, 6]
   8 
   9       print 'list normal:', list
  10       sort(list, 0)
  11       print 'sort by [0]:', list
  12       sort(list, 1)
  13       print 'sort by [1]:', list
  14 
  15       print
  16 
  17       print "dict normal:", dict
  18       sort(dict, 'a')
  19       print "sort by 'a':",  dict
  20       sort(dict, 'b')
  21       print "sort by 'b':",  dict
  22 
  23       print
  24 
  25       print 'dumb normal:', dumb
  26       sort(dumb)
  27       print 'normal sort:', dumb

Returning the sorted list is cheap (it's just an extra reference, since Python, fortunately, never does any copying of data unless you specifically request it) and offers uniform behavior between in-place and non-in-place cases. Often, we only want to do:

返回排序的类表是便宜的(幸运的是,它仅仅是一个额外的引用。因为python 从来不做任何数据拷贝,除非你特意要求它)并且在合适的和不合适的情况中,提供统一的行为。 经常的,我们仅仅想去做:

for x in sort(something, inplace=0): 

Returning a reference to the sorted data gives us just the right amount of flexibility for this.

为了它返回一个引用到排序的数据给我们仅仅正确的灵活的总量。

参考 See Also

PyCkBk-2-4--Recipe 2.4 and PyCkBk-4-10--Recipe 4.10.