【python排序函数】是什么? — 认识 Python 的排序利器

在 Python 中,对数据进行排序是一项非常常见的操作。Python 提供了两种主要的方式来完成这项任务,它们就像是处理同一问题的两把不同的工具:

1. 列表对象的方法:list.sort()

这是列表(list)对象自身提供的一个方法。它的核心特点是“原地”排序,也就是说,它会直接修改调用它的那个列表对象,而不是创建一个新的排序好的列表。

  • 调用方式:直接在列表对象后面加上 .sort()
  • 返回值:它没有返回一个排序好的列表,而是返回 None。这很重要,因为它意味着你不能直接将 list.sort() 的结果赋值给另一个变量来获取排序后的列表(那样只会得到 None)。
  • 适用对象:只能用于列表(list)类型。

2. 内置函数:sorted()

这是一个 Python 的内置函数,不属于任何特定的对象类型。它的核心特点是“返回新的列表”,它会接收一个可迭代对象(不仅仅是列表),然后返回一个新的、排序好的列表,而不会改变原始的可迭代对象。

  • 调用方式:直接调用函数名 sorted(),并将需要排序的可迭代对象作为第一个参数传入。
  • 返回值:返回一个包含所有元素的新列表,元素按照顺序排列。
  • 适用对象:可以用于任何可迭代对象,包括列表、元组、字符串、集合、字典(排序的是键)等。

两者核心区别总结:

  • 修改对象: list.sort() 原地修改列表;sorted() 返回新列表,不修改原对象。
  • 适用范围: list.sort() 仅用于列表;sorted() 可用于任何可迭代对象。
  • 返回值: list.sort() 返回 Nonesorted() 返回一个新的排序好的列表。

理解这两者的区别是掌握 Python 排序的关键。选择哪一个取决于你的具体需求:是想直接修改原列表以节省内存,还是需要保留原列表并获得一个排序后的副本。

【python排序函数】为什么要有两种不同的排序方式?

Python 提供 list.sort()sorted() 两种不同的排序方式,并非冗余,而是为了满足不同的编程场景和需求:

1. 内存效率与原地修改:

当处理非常大的列表时,创建一个全新的排序列表可能会消耗大量的内存。list.sort() 通过直接在原列表上进行排序,避免了额外内存的分配,这在内存受限或追求效率的场景下非常有用。如果你确定不再需要原始顺序的列表,原地排序是更好的选择。

相反,如果你需要在排序后仍然访问原始顺序的数据,或者不想改变原始数据(这在函数式编程或避免副作用时很重要),那么 sorted() 返回一个新列表的特性就非常有价值了。

2. 灵活性与适用范围:

sorted() 函数的强大之处在于它可以接收任何可迭代对象。这意味着你可以方便地对元组、字符串中的字符、集合中的元素,甚至字典的键进行排序,而不需要先将它们转换为列表。它提供了一个通用的排序接口。

list.sort() 专门为列表设计,因为它能直接利用列表底层的结构进行高效的原地修改。这是列表作为一种可变序列特有的能力。

总结为什么:

拥有这两种方法让 Python 在排序方面既灵活又高效:

  • list.sort() 适用于需要原地修改列表且关注内存使用的场景。
  • sorted() 适用于需要获取新排序结果、保留原数据或排序非列表可迭代对象的场景。

它们各自有明确的适用场景和优势,共同构成了 Python 强大的排序能力。

【python排序函数】哪里可以使用它们? — 适用范围详解

了解了两种排序函数的功能,接下来看看它们具体可以在哪些“地方”或“对象”上使用:

list.sort() 的使用地点:

正如其名称所示,list.sort() 是列表对象的一个方法。所以,它只能在已经存在的列表对象上调用。

例如:

my_list = [3, 1, 4, 1, 5, 9]
my_list.sort() # 直接对 my_list 进行原地排序

你不能在元组、字符串、集合或字典等其他类型的对象上调用 .sort() 方法,否则会引发 AttributeError。

sorted() 的使用地点:

sorted() 函数是一个内置函数,它的第一个参数接受任何可迭代对象。这意味着它的应用范围非常广泛:

  • 列表 (list):

    my_list = [3, 1, 4, 1, 5, 9]
    new_list = sorted(my_list) # new_list 是 [1, 1, 3, 4, 5, 9],my_list 不变

  • 元组 (tuple):

    my_tuple = (3, 1, 4, 1, 5, 9)
    new_list = sorted(my_tuple) # new_list 是 [1, 1, 3, 4, 5, 9] (注意返回的是列表)

  • 字符串 (str): (排序的是字符)

    my_string = "python"
    new_list = sorted(my_string) # new_list 是 ['h', 'n', 'o', 'p', 't', 'y']

  • 集合 (set): (集合本身无序,排序后得到有序列表)

    my_set = {3, 1, 4, 1, 5, 9}
    new_list = sorted(my_set) # new_list 是 [1, 3, 4, 5, 9] (注意集合会自动去重)

  • 字典 (dict): (默认排序字典的键)

    my_dict = {'c': 3, 'a': 1, 'b': 2}
    new_list = sorted(my_dict) # new_list 是 ['a', 'b', 'c']
    # 如果想排序字典的值或键值对,需要配合 key 参数或适当转换 (后面会讲)

  • 其他可迭代对象: 文件对象、生成器、range() 等。

所以,如果你需要对列表以外的序列或集合类型进行排序并获取一个排序后的列表结果,sorted() 是你的不二之选。

【python排序函数】如何使用它们? — 基础与进阶用法

掌握了是什么和为什么,最重要的就是“如何”去使用这两个函数了。它们的基础用法非常简单,但通过参数可以实现强大的自定义排序。

基础用法:

默认情况下,排序是升序进行的,元素之间使用标准的比较操作符 (<, >)。

# list.sort()
my_list = [5, 2, 8, 1, 9]
my_list.sort()
print(my_list) # 输出: [1, 2, 5, 8, 9]

# sorted()
my_data = (5, 2, 8, 1, 9)
new_list = sorted(my_data)
print(new_list) # 输出: [1, 2, 5, 8, 9]
print(my_data) # 输出: (5, 2, 8, 1, 9) (原元组不变)

降序排序:使用 reverse=True 参数

两个函数都接受一个布尔型参数 reverse。当 reverse=True 时,排序结果将是降序。

# list.sort() 降序
my_list = [5, 2, 8, 1, 9]
my_list.sort(reverse=True)
print(my_list) # 输出: [9, 8, 5, 2, 1]

# sorted() 降序
my_data = [5, 2, 8, 1, 9]
new_list = sorted(my_data, reverse=True)
print(new_list) # 输出: [9, 8, 5, 2, 1]

自定义排序:使用 key 参数

这是排序函数最强大的功能之一。key 参数接受一个函数,这个函数会作用于待排序序列中的每一个元素,生成一个用于比较的“键”。排序过程实际上是根据这些生成的“键”来排序,但返回的结果仍然是原始的元素。

key 函数必须接受一个参数(代表序列中的一个元素),并返回一个值(用于比较的键)。

key 参数的常见用法示例:

  • 按字符串长度排序:

    words = ["apple", "banana", "cherry", "date"]
    # 按长度升序
    words.sort(key=len)
    print(words) # 输出: ['date', 'apple', 'banana', 'cherry']

    # 使用 sorted 按长度降序
    words_sorted_by_len_desc = sorted(words, key=len, reverse=True)
    print(words_sorted_by_len_desc) # 输出: ['cherry', 'banana', 'apple', 'date']

  • 不区分大小写排序字符串:

    names = ["Alice", "bob", "Charlie", "David"]
    # 使用 str.lower 作为 key
    names.sort(key=str.lower)
    print(names) # 输出: ['Alice', 'bob', 'Charlie', 'David'] (按字母序,不区分大小写)

  • 使用 lambda 函数进行简单自定义排序:

    data = [(-2, 5), (1, 9), (-4, 2), (3, 7)]
    # 按元组的第二个元素排序
    sorted_by_second = sorted(data, key=lambda item: item[1])
    print(sorted_by_second) # 输出: [(-4, 2), (-2, 5), (3, 7), (1, 9)]

    # 按元素的绝对值排序数字列表
    numbers = [-3, 1, -2, 4, 0]
    sorted_by_abs = sorted(numbers, key=lambda x: abs(x))
    print(sorted_by_abs) # 输出: [0, 1, -2, -3, 4] (注意 -2 和 -3 的相对位置,这是因为 Python 排序是稳定的)

  • 排序复杂对象 (如字典、自定义类的实例):

    students = [{'name': 'Alice', 'age': 20, 'score': 85},
    {'name': 'Bob', 'age': 22, 'score': 90},
    {'name': 'Charlie', 'age': 20, 'score': 88}]

    # 按年龄排序
    sorted_by_age = sorted(students, key=lambda student: student['age'])
    print(sorted_by_age)
    # 输出: [{'name': 'Alice', 'age': 20, 'score': 85}, {'name': 'Charlie', 'age': 20, 'score': 88}, {'name': 'Bob', 'age': 22, 'score': 90}]

    # 按分数排序
    sorted_by_score = sorted(students, key=lambda student: student['score'], reverse=True)
    print(sorted_by_score)
    # 输出: [{'name': 'Bob', 'age': 22, 'score': 90}, {'name': 'Charlie', 'age': 20, 'score': 88}, {'name': 'Alice', 'age': 20, 'score': 85}]

    对于自定义类,你需要定义一个函数或 lambda 来访问用于排序的属性:

    class Person:
    def __init__(self, name, height):
    self.name = name
    self.height = height
    def __repr__(self): # 方便打印
    return f"Person({self.name}, {self.height})"

    people = [Person("Alice", 165), Person("Bob", 180), Person("Charlie", 175)]
    # 按身高排序
    sorted_by_height = sorted(people, key=lambda p: p.height)
    print(sorted_by_height) # 输出: [Person(Alice, 165), Person(Charlie, 175), Person(Bob, 180)]

  • 按多个条件排序:

    key 函数可以返回一个元组。Python 会按照元组元素的顺序进行比较:先比较第一个元素,如果相等,再比较第二个元素,依此类推。这可以实现多级排序。

    回到学生列表,先按年龄排序,年龄相同的按分数排序:

    students = [{'name': 'Alice', 'age': 20, 'score': 85},
    {'name': 'Bob', 'age': 22, 'score': 90},
    {'name': 'Charlie', 'age': 20, 'score': 88},
    {'name': 'David', 'age': 22, 'score': 88}]

    # 先按年龄升序,年龄相同按分数降序
    sorted_multi = sorted(students, key=lambda student: (student['age'], -student['score'])) # 分数前加负号实现降序
    print(sorted_multi)
    # 输出: [{'name': 'Alice', 'age': 20, 'score': 85}, {'name': 'Charlie', 'age': 20, 'score': 88}, {'name': 'David', 'age': 22, 'score': 88}, {'name': 'Bob', 'age': 22, 'score': 90}]

通过灵活运用 reversekey 参数,几乎可以实现任何你想要的排序逻辑。

【python排序函数】有多少种方式使用 key 参数? / 如何选择 key 函数?

虽然 key 参数只接受一个函数,但这个函数可以有多种形式,从而实现不同的排序逻辑。选择哪种方式取决于你的数据结构和排序需求:

  • 使用内置函数: 对于一些常见的需求,可以直接使用 Python 的内置函数作为 key,如 len (按长度排序), str.lower (按不区分大小写排序)。这是最简洁的方式。
  • 使用对象的特定方法: 如果待排序对象是字符串,你可能想使用 str.upperstr.strip 等方法处理后进行比较。
  • 使用 lambda 匿名函数: 对于简单的、一次性的自定义逻辑,lambda 函数非常方便。例如,按元组的第 N 个元素排序,按字典的某个键的值排序等。这是最常用的方式之一。
  • 使用普通的命名函数: 如果排序逻辑比较复杂,或者需要在多个地方复用,可以定义一个普通的函数作为 key。

    def get_score(student_dict):
    return student_dict['score']
    sorted_by_score = sorted(students, key=get_score)

  • 使用 operator 模块的函数: operator 模块提供了一些函数,可以更方便地创建访问对象属性或索引的 key 函数,有时比 lambda 更清晰(尤其是在排序多个元素时)。

    import operator
    # 按元组的第二个元素排序 (等价于 lambda item: item[1])
    sorted_by_second = sorted(data, key=operator.itemgetter(1))

    # 按字典的 'age' 键排序 (等价于 lambda student: student['age'])
    sorted_by_age = sorted(students, key=operator.itemgetter('age'))

    # 多级排序 (先按年龄,再按分数) (等价于 lambda student: (student['age'], student['score']))
    sorted_multi = sorted(students, key=operator.itemgetter('age', 'score'))

    operator.attrgetter() 用于按对象属性排序。

    class Person:
    def __init__(self, name, height):
    self.name = name
    self.height = height
    def __repr__(self): return f"Person({self.name}, {self.height})"
    people = [Person("Alice", 165), Person("Bob", 180)]
    sorted_by_height = sorted(people, key=operator.attrgetter('height'))

选择哪种 key 函数的方式主要取决于代码的可读性、简洁性以及逻辑的复杂程度。对于简单的单条件排序,内置函数或 lambda 函数通常足够;对于复杂的对象或多条件排序,operator 模块或命名函数可能更具优势。

【python排序函数】怎么保证排序的稳定性? / Timsort

Python 的排序算法有一个重要的特性:它是稳定的 (stable)。这意味着如果两个元素在排序前的顺序是 a 然后 b,并且它们的“键”在排序过程中被判断为相等,那么在排序后的结果中,a 仍然会在 b 的前面。原始的相对位置会被保留。

为什么稳定性重要?

稳定性在进行多级排序时尤其有用。例如,你首先按年龄排序一个学生列表,然后再对这个已经按年龄排序的列表按分数排序。如果排序算法是稳定的,那么在年龄相同的学生组内,他们的相对顺序会保持为按分数排序后的顺序。如果排序不稳定,第二次排序可能会打乱年龄相同学生之间原本已经按分数排好的顺序。

Python 使用的排序算法:Timsort

Python 使用一个名为 Timsort 的混合排序算法。Timsort 是 Peter Peters 于 2002 年为 Python 发明的一种排序算法,它结合了归并排序 (Merge Sort) 和插入排序 (Insertion Sort) 的优点。

  • 归并排序: 在处理大数据块时效率较高,并且是稳定的。
  • 插入排序: 在处理小数据块或部分已排序的数据时效率很高,并且是稳定的。

Timsort 会将数据分成若干个称为“run”的已排序子序列,然后利用插入排序对这些 run 进行优化,最后使用改进的归并排序将这些 run 合并起来。

因为 Timsort 基于归并排序和插入排序(这两种都是稳定的排序算法),所以 Timsort 也是稳定的。这是 Python 排序函数的一个重要内部细节,保证了复杂排序场景下的可预测行为。

【python排序函数】怎么处理排序中的常见问题? (比如混合类型)

虽然 Python 的排序函数很强大,但在处理一些特殊情况时,可能会遇到问题。最常见的问题之一是尝试直接排序包含混合数据类型的序列。

处理混合类型:

Python 3 中,不同类型之间默认是没有明确的比较规则的(例如,整数不能直接与字符串比较)。如果你尝试直接排序一个包含整数和字符串的列表,会引发 TypeError

mixed_list = [1, 'a', 2, 'b']
# mixed_list.sort() # 会引发 TypeError
# sorted(mixed_list) # 也会引发 TypeError

解决方法:使用 key 函数提供统一的比较键。

你可以编写一个 key 函数,它能够处理不同类型的输入,并返回一个可以相互比较的值。一种常见的做法是将所有元素转换为字符串或在比较前定义一个类型优先级。

def mixed_type_key(element):
if isinstance(element, int):
return (0, element) # 数字优先级高,放在前面,按数字值排序
elif isinstance(element, str):
return (1, element) # 字符串优先级低,放在后面,按字符串值排序
else:
return (2, str(element)) # 其他类型再往后排

mixed_list = [1, 'apple', 2, 'banana', 0, 'cherry', 3.14, None]
sorted_mixed = sorted(mixed_list, key=mixed_type_key)
print(sorted_mixed) # 输出: [0, 1, 2, 'apple', 'banana', 'cherry', 3.14, None] (注意 None 和 3.14 的位置取决于它们 str() 后的比较)

通过返回一个元组作为 key,我们首先按元素的类型“分组”,然后再在同类型内部进行排序。

其他注意事项:

  • 不可比较的元素: 如果你的数据中包含不能相互比较的自定义对象,你需要为这些对象定义比较方法 (如 __lt__, __gt__ 等),或者使用 key 函数提取可比较的属性。
  • key 函数的性能: key 函数会作用于序列中的每一个元素。如果 key 函数计算复杂,可能会影响排序的整体性能。尽量保持 key 函数的简洁高效。
  • 原地排序的副作用: 记住 list.sort() 会修改原列表。如果在排序后还需要原列表的原始顺序,务必使用 sorted() 或先复制一份列表再调用 .sort()

掌握这些常见问题及其解决方法,能帮助你更有效地使用 Python 的排序函数处理各种复杂的数据排序任务。

python排序函数