The list built-in type provides a sort method for ordering the items in a list instance based on a variety of criteria. By default, sort will order a list’s contents by the natural ascending order of the items. For example, here I sort a list of integers from smallest to largest:
列表内置类型提供了一种排序方法,用于根据各种条件对列表实例中的元素进行排序。默认情况下,sort将按照元素的自然升序对列表内容进行排序。例如,这里我从最小到最大对一个整数列表进行排序:
numbers = [93, 86, 11, 68, 70]
numbers.sort()
print(numbers)
>>>
[11, 68, 70, 86, 93]
The sort method works for nearly all built-in types (strings, floats, etc.) that have a natural ordering to them. What does sort do with objects? For example, here I define a class—including a repr method so instances are printable; see Item 75: “Use repr Strings for Debugging Output”—to represent various tools you may need to use on a construction site:
sort方法几乎适用于所有具有自然排序的内置类型(字符串、浮点数等)。sort对对象做了什么呢?例如,这里我定义了一个类(包括repr方法,这样实例就可以打印了;参见第75项:“使用repr字符串进行调试输出”),它表示在建筑工地可能需要使用的各种工具:
class Tool:
def __init__(self, name, weight):
self.name = name
self.weight = weight
def __repr__(self):
return f'Tool({self.name!r}, {self.weight})'
tools = [
Tool('level', 3.5), # 水平
Tool('hammer', 1.25), # 锤子
Tool('screwdriver', 0.5), # 螺丝刀
Tool('chisel', 0.25), # 凿子
]
Sorting objects of this type doesn’t work because the sort method tries to call comparison special methods that aren’t defined by the class:
这种类型的对象无法进行排序,因为sort方法尝试调用类中用于比较的魔术方法(这个方法没有被定义):
tools.sort()
>>>
Traceback ...
TypeError: '<' not supported between instances of 'Tool' and 'Tool'
If your class should have a natural ordering like integers do, then you can define the necessary special methods (see Item 73: “Know How to Use heapq for Priority Queues” for an example) to make sort work without extra parameters. But the more common case is that your objects may need to support multiple orderings, in which case defining a natural ordering really doesn’t make sense.
如果你的类像整数一样有一个自然的顺序,那么你可以定义必要的特殊方法(参见Item 73:“知道如何为优先队列使用heapq”的示例),使排序工作不需要额外的参数。但更常见的情况是,您的对象可能需要支持多种排序,在这种情况下,定义自然排序规则确实没有意义。
Often there’s an attribute on the object that you’d like to use for sorting. To support this use case, the sort method accepts a key parameter that’s expected to be a function. The key function is passed a single argument, which is an item from the list that is being sorted. The return value of the key function should be a comparable value (i.e., with a natural ordering) to use in place of an item for sorting purposes.
通常在对象上有一个你想用来排序的属性。为了支持这个用例,sort方法接受一个key参数,该参数通常用于接受一个函数。列表中待排序的元素作为参数传递到key函数中,key函数的返回值应该是一个可比较的值(也就是具有自然排序)。
Here, I use the lambda keyword to define a function for the key parameter that enables me to sort the list of Tool objects alphabetically by their name:
在这里,我使用lambda关键字为key参数传递了一个函数,使我能够按名称的字母顺序对Tool对象列表进行排序:
print('Unsorted:', repr(tools))
tools.sort(key=lambda x: x.name)
print('\nSorted: ', tools)
>>>
Unsorted:[Tool('level', 3.5),
Tool('hammer', 1.25),
Tool('screwdriver', 0.5),
Tool('chisel', 0.25)]
Sorted: [Tool('chisel', 0.25),
Tool('hammer', 1.25),
Tool('level', 3.5),
Tool('screwdriver', 0.5)]
I can just as easily define another lambda function to sort by weight and pass it as the key parameter to the sort method:
我可以很容易地定义另一个lambda函数来按重量排序,并将其作为key参数传递给sort方法:
tools.sort(key=lambda x: x.weight)
print('By weight:', tools)
>>>
By weight: [Tool('chisel', 0.25),
Tool('screwdriver', 0.5),
Tool('hammer', 1.25),
Tool('level', 3.5)]
Within the lambda function passed as the key parameter you can access attributes of items as I’ve done here, index into items (for sequences, tuples, and dictionaries), or use any other valid expression.
在作为关键参数传递的lambda函数中,您可以像我做的那样访问元素的属性,对元素进行索引(用于序列、元组和字典),或使用任何其他有效表达式。
For basic types like strings, you may even want to use the key function to do transformations on the values before sorting. For example, here I apply the lower method to each item in a list of place names to ensure that they’re in alphabetical order, ignoring any capitalization (since in the natural lexical ordering of strings, capital letters come before lowercase letters):
对于像字符串这样的基本类型,您甚至可以在排序之前使用key函数对值进行转换。例如,在这里,我对places列表中的每一项应用了lower方法,以确保它们不区分大小写按字母顺序排列(因为在字符串的自然词汇顺序中,大写字母在小写字母之前):
places = ['home', 'work', 'New York', 'Paris']
places.sort()
print('Case sensitive: ', places) # 区分大小写
places.sort(key=lambda x: x.lower())
print('Case insensitive:', places) # 不区分大小写
>>>
Case sensitive: ['New York', 'Paris', 'home', 'work']
Case insensitive: ['home', 'New York', 'Paris', 'work']
Sometimes you may need to use multiple criteria for sorting. For example, say that I have a list of power tools and I want to sort them first by weight and then by name. How can I accomplish this?
有时您可能需要使用多个条件进行排序。例如,假设我有一个电动工具列表,我想先按重量然后按名称对它们进行排序。我该怎么做呢?
power_tools = [
Tool('drill', 4),
Tool('circular saw', 5),
Tool('jackhammer', 40),
Tool('sander', 4),
]
The simplest solution in Python is to use the tuple type. Tuples are immutable sequences of arbitrary Python values. Tuples are comparable by default and have a natural ordering, meaning that they implement all of the special methods, such as lt, that are required by the sort method. Tuples implement these special method comparators by iterating over each position in the tuple and comparing the corresponding values one index at a time. Here, I show how this works when one tool is heavier than another:
Python中最简单的解决方案是使用tuple类型。元组是不可变序列,默认情况下元组是可比较的并且是具有自然排序规则的。这意味着它实现了sort方法所必须的所有魔术方法(如lt),元组通过迭代它的每个位置并一次比较一个索引来实现这些魔术方法比较器。在这里,我将通过一个示例(一个工具比另一个重)来展示这个工作原理:
saw = (5, 'circular saw')
jackhammer = (40, 'jackhammer')
assert not (jackhammer < saw) # Matches expectations
If the first position in the tuples being compared are equal—weight in this case—then the tuple comparison will move on to the second position, and so on:
如果元组中的第一个位置相等(在本例中是weight),则将比较移到第二个位置,以此类推:
drill = (4, 'drill')
sander = (4, 'sander')
assert drill[0] == sander[0] # Same weight
assert drill[1] < sander[1] # Alphabetically less
assert drill < sander # Thus, drill comes first
You can take advantage of this tuple comparison behavior in order to sort the list of power tools first by weight and then by name. Here, I define a key function that returns a tuple containing the two attributes that I want to sort on in order of priority:
您可以利用元组的这个比较特性,先按重量再按名称对电动工具列表进行排序。在这里,我定义了一个key函数,它返回一个元组,其中包含我想按优先级排序的两个属性:
power_tools.sort(key=lambda x: (x.weight, x.name))
print(power_tools)
>>>
[Tool('drill', 4),
Tool('sander', 4),
Tool('circular saw', 5),
Tool('jackhammer', 40)]
One limitation of having the key function return a tuple is that the direction of sorting for all criteria must be the same (either all in ascending order, or all in de scending order). If I provide the reverse parameter to the sort method, it will affect both criteria in the tuple the same way (note how 'sander' now comes before 'drill' instead of after):
使用key函数返回元组进行排序的一个限制是,所有条件的排序方向必须是相同的(要么全部按升序排序,要么全部按降序排序)。如果我为sort方法提供reverse参数,它将以相同的方式影响元组中的两个条件(注意'sander'现在是如何出现在'drill'之前而不是之后的):
power_tools.sort(key=lambda x: (x.weight, x.name),
reverse=True) # Makes all criteria descending
print(power_tools)
>>>
[Tool('jackhammer', 40),
Tool('circular saw', 5),
Tool('sander', 4),
Tool('drill', 4)]
For numerical values it’s possible to mix sorting directions by using the unary minus operator in the key function. This negates one of the values in the returned tuple, effectively reversing its sort order while leaving the others intact. Here, I use this approach to sort by weight descending, and then by name ascending (note how 'sander' now comes after 'drill' instead of before):
对于数值而言,可以通过在key函数中使用一元减号运算符来混合排序方向。这样将对返回元组中的一个值进行负运算,从而有效地反转其排列顺序,同时保持其他值不变。在这里,我使用这种方法按权重降序排序,然后按名称升序排序(注意'sander'现在是如何在'drill'之后而不是之前出现的):
power_tools.sort(key=lambda x: (-x.weight, x.name))
print(power_tools)
>>>
[Tool('jackhammer', 40),
Tool('circular saw', 5),
Tool('drill', 4),
Tool('sander', 4)]
Unfortunately, unary negation isn’t possible for all types. Here, I try to achieve the same outcome by using the reverse argument to sort by weight descending and then negating name to put it in ascending order:
遗憾的是,负数运算并不适用于所有类型。在这里,我尝试通过使用reverse参数按重量降序排序,然后对名称负运算进行升序排序来实现相同的结果:
power_tools.sort(key=lambda x: (x.weight, -x.name),
reverse=True)
>>>
Traceback ...
TypeError: bad operand type for unary -: 'str'
For situations like this, Python provides a stable sorting algorithm. The sort method of the list type will preserve the order of the input list when the key function returns values that are equal to each other. This means that I can call sort multiple times on the same list to combine different criteria together. Here, I produce the same sort ordering of weight descending and name ascending as I did above but by using two separate calls to sort:
对于这种情况,Python提供了一个稳定的排序算法。当key函数返回相等的值时,列表类型的sort方法将保留输入列表的顺序。这意味着我可以在同一个列表上多次调用sort将不同的条件组合在一起。在这里,我实现了和上面一样的排序(按重量降序和名称升序),但调用了两次sort方法:
power_tools.sort(key=lambda x: x.name) # Name ascending
power_tools.sort(key=lambda x: x.weight,
reverse=True) # Weight descending
print(power_tools)
>>>
[Tool('jackhammer', 40),
Tool('circular saw', 5),
Tool('drill', 4),
Tool('sander', 4)]
To understand why this works, note how the first call to sort puts the names in alphabetical order:
要理解其工作原理,请注意第一次sort调用是如何将名称按字母顺序排序的:
power_tools.sort(key=lambda x: x.name)
print(power_tools)
>>>
[Tool('circular saw', 5),
Tool('drill', 4),
Tool('jackhammer', 40),
Tool('sander', 4)]
When the second sort call by weight descending is made, it sees that both 'sander' and 'drill' have a weight of 4. This causes the sort method to put both items into the final result list in the same order that they appeared in the original list, thus preserving their relative ordering by name ascending:
当按重量递减进行第二次sory调用时,可以看到“sander”和“drill”的重量都为4。这导致sort方法将这两项以它们在原始列表中出现的相同顺序放入最终结果列表中,从而保持它们按名称升序的相对顺序:
power_tools.sort(key=lambda x: x.weight,
reverse=True)
print(power_tools)
>>>
[Tool('jackhammer', 40),
Tool('circular saw', 5),
Tool('drill', 4),
Tool('sander', 4)]
This same approach can be used to combine as many different types of sorting criteria as you’d like in any direction, respectively. You just need to make sure that you execute the sorts in the opposite sequence of what you want the final list to contain. In this example, I wanted the sort order to be by weight descending and then by name ascending, so I had to do the name sort first, followed by the weight sort.
使用相同的方法可以将多种不同类型的排序条件按你想要的顺序组合在一起。您只需要确保执行排序的顺序与您希望最终列表包含的顺序相反。在这个例子中,我希望排序顺序是按重量降序,然后按名称升序,所以我必须先执行名称排序,然后是重量排序。
That said, the approach of having the key function return a tuple, and using unary negation to mix sort orders, is simpler to read and requires less code. I recommend only using multiple calls to sort if it’s absolutely necessary.
也就是说,让key函数返回一个元组,并使用一元负数运算符来混合排序的方法更容易阅读,需要的代码更少。我建议只在绝对必要的情况下使用多次调用sort方法进行排序。
Things to Remember
要记住的事
✦ The sort method of the list type can be used to rearrange a list’s contents by the natural ordering of built-in types like strings, integers, tuples, and so on.
✦ The sort method doesn’t work for objects unless they define a natural ordering using special methods, which is uncommon.
✦ The key parameter of the sort method can be used to supply a helper function that returns the value to use for sorting in place of each item from the list.
✦ Returning a tuple from the key function allows you to combine multiple sorting criteria together. The unary minus operator can be used to reverse individual sort orders for types that allow it.
✦ For types that can’t be negated, you can combine many sorting criteria together by calling the sort method multiple times using different key functions and reverse values, in the order of lowest rank sort call to highest rank sort call.
✦ 列表类型的sort方法可用于按照内置类型(如字符串、整数、元组等)的自然顺序重新排列列表的内容。
✦ sort方法并不适用于对象,除非它们使用魔术方法定义了一个自然的排序,但这是不常用的。
✦ sort方法的key参数可以传递一个助手函数,该函数返回一个用于排序的值,以代替列表中的每一项。
✦ 从key函数返回元组,让您可以将多个排序条件组合在一起。一元减号运算符可用于反转排序,但它只适用于允许使用它的个别类型。
✦ 对于不能使用负运算的类型,您可以将许多排序条件组合在一起,通过使用不同的key函数和返回值多次调用sort方法(按照从最低等级排序调用到最高等级排序调用的顺序。)