Python 排序:sorted()、list.sort() 与自定义排序完整指南
Updated on
对数据进行排序是编程中最基础的操作之一,但在 Python 中该选 sorted() 函数还是 list.sort() 方法,经常会让开发者感到困惑。无论你是在给学生成绩排名、整理商品价格,还是处理文件列表,理解 Python 的排序机制都能节省时间并避免 Bug。本指南将覆盖 Python 排序的各个方面:从简单的数字列表到复杂的自定义比较,并提供可立即上手的实用示例。
sorted() vs list.sort():关键差异
Python 提供了两种主要的排序方式,它们看起来相似,但行为上有本质区别:sorted() 会创建并返回一个新的已排序列表,同时保持原数据不变;list.sort() 则会原地修改列表,并返回 None。
# sorted() returns a new list
numbers = [3, 1, 4, 1, 5]
sorted_nums = sorted(numbers)
print(sorted_nums) # [1, 1, 3, 4, 5]
print(numbers) # [3, 1, 4, 1, 5] - original unchanged
# list.sort() modifies in place
numbers = [3, 1, 4, 1, 5]
result = numbers.sort()
print(numbers) # [1, 1, 3, 4, 5] - original modified
print(result) # None这个区别会影响内存效率与数据保留策略。当你需要保留原始列表,或要对不可变序列(如 tuple)进行排序时,使用 sorted();当你处理大型列表、不希望额外复制占用内存时,使用 list.sort()。
# sorted() works with any iterable
tuple_data = (42, 17, 93, 8)
sorted_list = sorted(tuple_data) # Returns [8, 17, 42, 93]
# list.sort() only works on lists
# tuple_data.sort() # AttributeError: 'tuple' object has no attribute 'sort'基础排序:数字、字符串与混合类型
Python 的默认排序行为遵循“自然顺序”:数字按数值大小排序,字符串按字典序排序;而混合类型列表在 Python 3 中需要显式处理,否则会报错。
# Numeric sorting
integers = [42, -7, 0, 93, 15]
print(sorted(integers)) # [-7, 0, 15, 42, 93]
floats = [3.14, 2.71, 1.41, 9.81]
print(sorted(floats)) # [1.41, 2.71, 3.14, 9.81]
# String sorting (lexicographic order)
words = ['zebra', 'apple', 'mango', 'banana']
print(sorted(words)) # ['apple', 'banana', 'mango', 'zebra']
# Mixed types cause errors in Python 3
mixed = [3, 'apple', 1.5]
# sorted(mixed) # TypeError: '<' not supported between instances如果字符串其实表示数字,并且你希望按数值而不是字母顺序排序,可以在排序时转换类型:
# String numbers sort alphabetically
string_nums = ['10', '2', '100', '21']
print(sorted(string_nums)) # ['10', '100', '2', '21']
# Convert to int for numeric sorting
print(sorted(string_nums, key=int)) # ['2', '10', '21', '100']使用 reverse=True 进行反向排序
sorted() 与 list.sort() 都支持 reverse 参数,用于反转排序顺序。该参数默认是 False(升序)。
scores = [88, 92, 75, 95, 82]
# Descending order using reverse=True
print(sorted(scores, reverse=True)) # [95, 92, 88, 82, 75]
# In-place descending sort
scores.sort(reverse=True)
print(scores) # [95, 92, 88, 82, 75]
# Reverse alphabetical order
names = ['Alice', 'David', 'Bob', 'Charlie']
print(sorted(names, reverse=True)) # ['David', 'Charlie', 'Bob', 'Alice']key 参数:自定义排序逻辑
key 参数接收一个函数,在比较之前会先对每个元素做一次“映射/提取”。这让你可以按特定属性、计算值或复杂规则排序,同时不需要修改原始数据。
# Sort by string length
words = ['python', 'is', 'awesome', 'for', 'data']
print(sorted(words, key=len)) # ['is', 'for', 'data', 'python', 'awesome']
# Sort by absolute value
numbers = [-5, -2, 3, -8, 1]
print(sorted(numbers, key=abs)) # [1, -2, 3, -5, -8]
# Sort case-insensitive
names = ['alice', 'Bob', 'CHARLIE', 'david']
print(sorted(names, key=str.lower)) # ['alice', 'Bob', 'CHARLIE', 'david']使用 Lambda 函数进行内联排序
Lambda 适合编写一次性的简短 key 函数,避免为简单转换单独定义函数的冗长。
# Sort tuples by second element
pairs = [(1, 'b'), (3, 'a'), (2, 'd'), (4, 'c')]
print(sorted(pairs, key=lambda x: x[1]))
# [(3, 'a'), (1, 'b'), (4, 'c'), (2, 'd')]
# Sort strings by last character
words = ['python', 'java', 'ruby', 'go']
print(sorted(words, key=lambda s: s[-1]))
# ['java', 'go', 'ruby', 'python']
# Sort by distance from target value
target = 50
values = [45, 62, 38, 55, 48]
print(sorted(values, key=lambda x: abs(x - target)))
# [48, 45, 55, 38, 62]按多个条件排序
当排序需要多个优先级时,可以让 key 函数返回一个 tuple。Python 会逐项比较 tuple,从而自然实现多级排序。
# Sort students by grade (descending), then name (ascending)
students = [
('Alice', 85),
('Bob', 92),
('Charlie', 85),
('David', 92)
]
sorted_students = sorted(students, key=lambda x: (-x[1], x[0]))
print(sorted_students)
# [('Bob', 92), ('David', 92), ('Alice', 85), ('Charlie', 85)]
# Sort dates by year (descending), then month (ascending)
dates = ['2024-03', '2023-12', '2024-01', '2023-08']
print(sorted(dates, key=lambda d: (-int(d[:4]), int(d[5:]))))
# ['2024-01', '2024-03', '2023-08', '2023-12']operator.itemgetter 与 operator.attrgetter
operator 模块为常见排序模式提供了更高效的替代方案,相比 lambda 往往更快,也更利于阅读。
from operator import itemgetter, attrgetter
# Sort by specific dictionary keys
data = [
{'name': 'Alice', 'age': 30, 'score': 85},
{'name': 'Bob', 'age': 25, 'score': 92},
{'name': 'Charlie', 'age': 30, 'score': 78}
]
# Sort by age, then score
sorted_data = sorted(data, key=itemgetter('age', 'score'))
print([d['name'] for d in sorted_data]) # ['Bob', 'Charlie', 'Alice']
# Sort by tuple elements
records = [(1, 'b', 300), (2, 'a', 100), (1, 'a', 200)]
print(sorted(records, key=itemgetter(0, 1)))
# [(1, 'a', 200), (1, 'b', 300), (2, 'a', 100)]对于对象属性,使用 attrgetter:
from operator import attrgetter
class Employee:
def __init__(self, name, salary, department):
self.name = name
self.salary = salary
self.department = department
def __repr__(self):
return f'{self.name}({self.department}, ${self.salary})'
employees = [
Employee('Alice', 75000, 'Engineering'),
Employee('Bob', 65000, 'Marketing'),
Employee('Charlie', 75000, 'Engineering')
]
# Sort by department, then salary (descending)
sorted_emps = sorted(employees, key=attrgetter('department'))
print(sorted_emps)排序字典
字典本身是无序的(尽管 Python 3.7+ 会保留插入顺序),但你可以对它的 keys、values 或 items 进行排序。
# Sort dictionary by keys
data = {'zebra': 3, 'apple': 1, 'mango': 2}
sorted_keys = sorted(data.keys())
print(sorted_keys) # ['apple', 'mango', 'zebra']
# Create new dict with sorted keys
sorted_dict = {k: data[k] for k in sorted_keys}
print(sorted_dict) # {'apple': 1, 'mango': 2, 'zebra': 3}
# Sort by values
sorted_by_value = sorted(data.items(), key=lambda x: x[1])
print(sorted_by_value) # [('apple', 1), ('mango', 2), ('zebra', 3)]
# Convert back to dictionary
sorted_dict = dict(sorted_by_value)对于更复杂的 value 类型:
# Sort dictionary by nested values
products = {
'laptop': {'price': 999, 'stock': 5},
'phone': {'price': 599, 'stock': 12},
'tablet': {'price': 399, 'stock': 8}
}
# Sort by price
by_price = sorted(products.items(), key=lambda x: x[1]['price'])
print([(name, info['price']) for name, info in by_price])
# [('tablet', 399), ('phone', 599), ('laptop', 999)]
# Sort by stock (descending)
by_stock = sorted(products.items(), key=lambda x: -x[1]['stock'])
print([(name, info['stock']) for name, info in by_stock])
# [('phone', 12), ('tablet', 8), ('laptop', 5)]排序 tuple 列表
tuple 的默认排序会从左到右逐元素比较。你也可以用 key 函数来定制行为。
# Default tuple sorting (compares element by element)
tuples = [(1, 'z'), (2, 'a'), (1, 'a'), (2, 'z')]
print(sorted(tuples))
# [(1, 'a'), (1, 'z'), (2, 'a'), (2, 'z')]
# Sort by second element only
print(sorted(tuples, key=lambda x: x[1]))
# [(2, 'a'), (1, 'a'), (1, 'z'), (2, 'z')]
# Sort by sum of numeric elements
coord_distances = [(1, 2), (3, 1), (2, 2), (1, 3)]
print(sorted(coord_distances, key=sum))
# [(1, 2), (2, 2), (1, 3), (3, 1)]排序字典列表
在列表中对字典排序,需要明确用哪个字段作为比较依据。
people = [
{'name': 'Alice', 'age': 30, 'city': 'New York'},
{'name': 'Bob', 'age': 25, 'city': 'San Francisco'},
{'name': 'Charlie', 'age': 30, 'city': 'Austin'}
]
# Sort by age
by_age = sorted(people, key=lambda x: x['age'])
print([p['name'] for p in by_age]) # ['Bob', 'Alice', 'Charlie']
# Sort by age (descending), then name (ascending)
by_age_name = sorted(people, key=lambda x: (-x['age'], x['name']))
print([f"{p['name']} ({p['age']})" for p in by_age_name])
# ['Alice (30)', 'Charlie (30)', 'Bob (25)']
# Using itemgetter (more efficient)
from operator import itemgetter
by_city = sorted(people, key=itemgetter('city'))
print([p['city'] for p in by_city])
# ['Austin', 'New York', 'San Francisco']排序对象列表
自定义类通常需要提供 key 函数来提取可比较的属性。
class Product:
def __init__(self, name, price, rating):
self.name = name
self.price = price
self.rating = rating
def __repr__(self):
return f'{self.name}(${self.price}, {self.rating}★)'
products = [
Product('Laptop', 999, 4.5),
Product('Phone', 599, 4.8),
Product('Tablet', 399, 4.2),
Product('Monitor', 299, 4.7)
]
# Sort by price
by_price = sorted(products, key=lambda p: p.price)
print(by_price)
# Sort by rating (descending)
by_rating = sorted(products, key=lambda p: -p.rating)
print(by_rating)
# Sort by value score (rating / price * 100)
by_value = sorted(products, key=lambda p: (p.rating / p.price) * 100, reverse=True)
print(by_value)或者,实现 __lt__(less than)方法来定义默认排序行为:
class Student:
def __init__(self, name, gpa):
self.name = name
self.gpa = gpa
def __lt__(self, other):
return self.gpa > other.gpa # Higher GPA comes first
def __repr__(self):
return f'{self.name}({self.gpa})'
students = [Student('Alice', 3.8), Student('Bob', 3.9), Student('Charlie', 3.7)]
print(sorted(students)) # [Bob(3.9), Alice(3.8), Charlie(3.7)]不区分大小写的字符串排序
字符串比较默认区分大小写,这会导致大写字母排在小写字母前面。可使用 str.lower() 或 str.casefold() 实现不区分大小写的排序。
# Case-sensitive sorting (default)
words = ['apple', 'Banana', 'cherry', 'Date']
print(sorted(words)) # ['Banana', 'Date', 'apple', 'cherry']
# Case-insensitive sorting
print(sorted(words, key=str.lower)) # ['apple', 'Banana', 'cherry', 'Date']
# Using casefold for Unicode handling
international = ['café', 'CAFÉ', 'Apple', 'apple']
print(sorted(international, key=str.casefold))稳定排序保证与 Timsort 算法
Python 使用 Timsort 算法,保证排序稳定性。所谓稳定,是指当多个元素的 key 相等时,它们在结果中的相对顺序与原始顺序一致。
# Stable sort preserves original order for equal elements
data = [
('Alice', 85),
('Bob', 92),
('Charlie', 85), # Same score as Alice
('David', 92) # Same score as Bob
]
# Sort by score only - original order preserved for ties
by_score = sorted(data, key=lambda x: x[1])
print(by_score)
# [('Alice', 85), ('Charlie', 85), ('Bob', 92), ('David', 92)]
# Alice appears before Charlie (both 85) because she came first稳定性还支持“多轮排序”:
# Multi-pass sorting using stability
records = [
{'name': 'Alice', 'dept': 'HR', 'salary': 60000},
{'name': 'Bob', 'dept': 'IT', 'salary': 75000},
{'name': 'Charlie', 'dept': 'HR', 'salary': 65000},
{'name': 'David', 'dept': 'IT', 'salary': 70000}
]
# First sort by salary
records.sort(key=lambda x: x['salary'], reverse=True)
# Then sort by department (stable, so salary order preserved within dept)
records.sort(key=lambda x: x['dept'])
for r in records:
print(f"{r['dept']}: {r['name']} (${r['salary']})")
# HR: Charlie ($65000)
# HR: Alice ($60000)
# IT: Bob ($75000)
# IT: David ($70000)性能对比:不同排序方法
Python 的排序在最坏情况下时间复杂度为 O(n log n),对大多数数据集都足够高效。不同方法适用于不同场景:
| Method | Time Complexity | Space | Use Case |
|---|---|---|---|
list.sort() | O(n log n) | O(1) | 原地排序,大列表 |
sorted() | O(n log n) | O(n) | 保留原始数据,排序任意 iterable |
heapq.nsmallest(k, items) | O(n log k) | O(k) | 从大数据集中取最小的 k 个元素 |
heapq.nlargest(k, items) | O(n log k) | O(k) | 从大数据集中取最大的 k 个元素 |
bisect.insort(list, item) | O(n) | O(1) | 通过插入维持有序列表 |
import heapq
# When you only need top 3 scores from 1 million records
scores = list(range(1000000))
top_3 = heapq.nlargest(3, scores) # Much faster than sorted()[-3:]
print(top_3) # [999999, 999998, 999997]
# When you need bottom 5 values
bottom_5 = heapq.nsmallest(5, scores)
print(bottom_5) # [0, 1, 2, 3, 4]当你需要频繁插入并维持有序列表时:
import bisect
# Maintain sorted list efficiently
sorted_list = []
for value in [5, 2, 8, 1, 9, 3]:
bisect.insort(sorted_list, value)
print(sorted_list) # [1, 2, 3, 5, 8, 9]functools.cmp_to_key:兼容旧式比较函数
Python 2 支持返回 -1、0、1 的比较函数;Python 3 主要使用 key 函数。不过 functools.cmp_to_key 可以把旧式比较函数转换为 key。
from functools import cmp_to_key
# Custom comparison: sort by remainder when divided by 3
def compare_mod3(x, y):
return (x % 3) - (y % 3)
numbers = [10, 11, 12, 13, 14, 15]
sorted_nums = sorted(numbers, key=cmp_to_key(compare_mod3))
print(sorted_nums) # [12, 15, 10, 13, 11, 14]
# Groups: [12,15] (mod 0), [10,13] (mod 1), [11,14] (mod 2)
# Complex comparison: version strings
def compare_versions(v1, v2):
parts1 = list(map(int, v1.split('.')))
parts2 = list(map(int, v2.split('.')))
for p1, p2 in zip(parts1, parts2):
if p1 != p2:
return p1 - p2
return len(parts1) - len(parts2)
versions = ['1.10.2', '1.9.0', '1.10.15', '2.0.0', '1.10']
sorted_versions = sorted(versions, key=cmp_to_key(compare_versions))
print(sorted_versions) # ['1.9.0', '1.10', '1.10.2', '1.10.15', '2.0.0']含 None 值的排序
当 None 与其他类型混在一起比较时会报错,需要显式处理:要么把 None 放到开头/结尾,要么用特定值替代。
# None values cause errors
data = [3, None, 1, 5, None, 2]
# sorted(data) # TypeError: '<' not supported between 'NoneType' and 'int'
# Place None values at the end
sorted_data = sorted(data, key=lambda x: (x is None, x))
print(sorted_data) # [1, 2, 3, 5, None, None]
# Place None values at the beginning
sorted_data = sorted(data, key=lambda x: (x is not None, x))
print(sorted_data) # [None, None, 1, 2, 3, 5]
# Replace None with specific value for sorting
sorted_data = sorted(data, key=lambda x: x if x is not None else float('-inf'))
print(sorted_data) # [None, None, 1, 2, 3, 5]对于可能缺失字段的字典:
records = [
{'name': 'Alice', 'score': 85},
{'name': 'Bob'}, # Missing score
{'name': 'Charlie', 'score': 92},
{'name': 'David'} # Missing score
]
# Sort with default value for missing keys
sorted_records = sorted(records, key=lambda x: x.get('score', 0))
print([r['name'] for r in sorted_records])
# ['Bob', 'David', 'Alice', 'Charlie']真实示例:学生表现排名
该示例把多种排序技巧组合起来,构建一个更完整的排名系统:
class StudentRecord:
def __init__(self, name, test_scores, attendance, extra_credit=0):
self.name = name
self.test_scores = test_scores
self.attendance = attendance
self.extra_credit = extra_credit
@property
def average_score(self):
return sum(self.test_scores) / len(self.test_scores) if self.test_scores else 0
@property
def final_grade(self):
base = self.average_score * 0.8 + self.attendance * 0.2
return min(100, base + self.extra_credit)
def __repr__(self):
return f'{self.name}: {self.final_grade:.1f}'
students = [
StudentRecord('Alice', [85, 90, 88], 95, 5),
StudentRecord('Bob', [92, 88, 95], 80, 0),
StudentRecord('Charlie', [78, 82, 80], 100, 10),
StudentRecord('David', [90, 92, 88], 90, 2),
StudentRecord('Eve', [85, 85, 85], 95, 0)
]
# Rank by final grade (highest first), then name
rankings = sorted(students, key=lambda s: (-s.final_grade, s.name))
print("Student Rankings:")
for rank, student in enumerate(rankings, 1):
print(f"{rank}. {student}")真实示例:处理文件列表
对文件元数据排序通常需要处理多种数据类型,并支持自定义排序规则:
import os
from datetime import datetime
class FileInfo:
def __init__(self, path):
self.name = os.path.basename(path)
self.path = path
self.size = os.path.getsize(path)
self.modified = os.path.getmtime(path)
self.extension = os.path.splitext(path)[1].lower()
def __repr__(self):
size_kb = self.size / 1024
mod_time = datetime.fromtimestamp(self.modified).strftime('%Y-%m-%d %H:%M')
return f'{self.name} ({size_kb:.1f} KB, {mod_time})'
# Simulate file information
files = [
FileInfo('/data/report.pdf'),
FileInfo('/data/summary.txt'),
FileInfo('/data/analysis.xlsx'),
FileInfo('/data/backup.pdf'),
]
# Sort by extension, then size (largest first)
by_type_size = sorted(files, key=lambda f: (f.extension, -f.size))
# Sort by modification time (most recent first)
by_recent = sorted(files, key=lambda f: -f.modified)
# Sort by size, handling different units
def format_size(bytes_size):
if bytes_size < 1024:
return f"{bytes_size} B"
elif bytes_size < 1024 * 1024:
return f"{bytes_size/1024:.1f} KB"
else:
return f"{bytes_size/(1024*1024):.1f} MB"
by_size = sorted(files, key=lambda f: f.size, reverse=True)
for f in by_size:
print(f"{f.name}: {format_size(f.size)}")使用 PyGWalker 进行交互式数据排序
当你处理的大型数据集需要可视化探索与交互式排序时,PyGWalker 可以将 pandas DataFrames 转换为类似 Tableau 的界面。你无需编写复杂排序逻辑,只要拖拽列即可完成排序、筛选与可视化分析:
import pandas as pd
import pygwalker as pyg
# Create sample sales data
sales_data = pd.DataFrame({
'product': ['Laptop', 'Phone', 'Tablet', 'Monitor', 'Keyboard'] * 100,
'region': ['North', 'South', 'East', 'West', 'Central'] * 100,
'revenue': [999, 599, 399, 299, 49] * 100,
'units_sold': [50, 120, 80, 95, 200] * 100,
'date': pd.date_range('2024-01-01', periods=500)
})
# Launch interactive visualization
pyg.walk(sales_data)PyGWalker 支持:
- 可视化反馈的多列排序
- 与排序结合的动态筛选
- 对计算字段排序而无需写代码
- 将排序后的视图导出为图表或表格
当你在探索不熟悉的数据集,或需要向非技术干系人展示数据规律(而对方不想运行 Python 代码)时,这种方式尤其有效。
FAQ
Python 中 sorted() 和 list.sort() 有什么区别?
sorted() 会返回一个新的已排序列表,适用于任何 iterable,并且不会修改原始数据。list.sort() 会在原列表上原地排序,返回 None,并且只适用于 list 对象。需要保留原数据或对 tuple 等非 list 序列排序时用 sorted();对大列表追求内存效率时用 list.sort()。
如何在 Python 中对列表进行降序排序?
在 sorted() 或 list.sort() 中添加 reverse=True 参数即可。例如:sorted([3, 1, 4], reverse=True) 会返回 [4, 3, 1]。该方式适用于所有具备自然顺序的数据类型,包括数字、字符串与日期。
可以按 value 对字典排序吗?
字典本身不具备传统意义上的“排序”,但你可以对其 items 按 value 排序:sorted(dict.items(), key=lambda x: x[1]),它会返回按 value 排序后的 tuple 列表。如有需要可再用 dict(sorted(...)) 转回字典(Python 3.7+ 才会保留该顺序)。
如何按指定 key 对“字典列表”排序?
使用 sorted() 并用 lambda 提取字段:sorted(list_of_dicts, key=lambda x: x['keyname'])。为获得更好的性能,可使用 operator.itemgetter:sorted(list_of_dicts, key=itemgetter('keyname'))。两者都支持按单个或多个 key 排序。
Python sort 的时间复杂度是多少?
Python 的 sorted() 与 list.sort() 使用 Timsort 算法,最坏情况下时间复杂度为 O(n log n),对于部分已排序数据,最好情况下可达 O(n)。空间复杂度方面:sorted() 为 O(n)(创建新列表),list.sort() 为 O(1)(原地排序)。当需要从大数据集中找 top k 元素时,heapq.nlargest() 可提供 O(n log k)。
排序时如何处理 None 值?
None 与其他类型混合比较会报错。可以用 key 函数将其放到末尾或开头:sorted(data, key=lambda x: (x is None, x)) 会把 None 放在末尾;sorted(data, key=lambda x: (x is not None, x)) 会把 None 放在开头。对可能缺失字段的字典,使用 x.get('key', default_value) 提供默认值。
Python 的排序是稳定的吗?
是的。Python 保证排序稳定:当 key 相同,元素会保持原始相对顺序。这使得“多轮排序”可行:你可以先按一个条件排序,再按另一个条件排序,而第一轮的相对顺序会在相同 key 的分组内被保留。该稳定性由 Timsort 算法保证。
结论
Python 的排序能力为高效组织数据提供了强大工具。理解 sorted() 与 list.sort() 的区别能帮助你做出更符合内存与数据保留需求的选择;而 key 参数与 lambda 函数则让你能够实现复杂而灵活的自定义排序逻辑。掌握稳定排序、多条件排序与性能特性,能让你在不同场景下选用最合适的方法。
无论你是在给学生成绩排名、处理文件元数据,还是分析业务指标,这些排序技巧都是数据处理工作流的基础。对于可视化数据探索与交互式排序,PyGWalker 通过拖拽式界面补充了编程式排序能力。
熟练掌握这些排序模式,你将能写出更整洁的代码、提升性能,并自信应对复杂的数据组织挑战。