Python & AI Tutorials Logo
Python 编程

37. 内置函数与实用工具

Python 提供了丰富的内置函数(built-in functions),它们始终可用,无需导入任何模块。这些函数构成了日常 Python 编程的基础,帮助你高效地处理数据、序列和集合。在本章中,我们将探索 Python 最有用的内置工具,并学习如何利用它们编写更简洁、更具表达力的代码。

理解 Python 的类型系统

在深入具体的内置函数之前,先了解 Python 如何组织其数据类型会很有帮助。这些知识将帮助你预测哪些操作适用于哪些类型,并在出错时理解错误信息。

Python 的数据类型可以从两个互补的角度来理解:

类型层级:类型之间如何关联

object

数值类型

序列类型

映射类型

集合类型

其他

int

float

complex

不可变

可变

str

tuple

range

list

dict

set

frozenset

bool

NoneType

这展示了 Python 如何根据类型“是什么”将它们组织成不同家族。

基于能力的视角:类型能做什么

对于内置函数而言,更重要的是类型“能做什么”:

所有 Python 对象

可迭代(iterable)
(可以用 for 循环)

不可迭代
(int, float, bool)

集合(collection)
(有长度)

其他可迭代对象
(generator, iterator)

序列(sequence)
(有序、可索引)

无序
(set, dict)

str

list

tuple

range

set

dict

关键能力:

  • 可迭代(iterable):可以用于 for 循环 → 可用于 sum()any()all()sorted()
  • 集合(collection):带有 len() 的可迭代对象 → 可用于 len()in 运算符
  • 序列(sequence):支持索引的集合 → 支持 [index] 和切片 [start:end]

为什么这很重要

内置函数需要特定的能力:

函数需要适用于
len()集合str, list, dict, set, tuple
sum()数字可迭代对象list, tuple, set, range, generator
sorted()可迭代对象str, list, dict, set, tuple
[index]序列str, list, tuple, range

理解这些类别可以帮助你:

  • 预测哪些函数适用于哪些类型
  • 理解诸如 “object is not iterable” 这类错误信息
  • 知道什么时候可以索引(\[0]) vs 什么时候只能循环(for)

37.1) 常见内置函数(len, sum, min, max, abs, round)

Python 最常用的内置函数能帮助你对数据执行常见操作,而无需编写循环或复杂逻辑。这些函数经过优化、可读性强,并构成 Pythonic 代码的基础。

37.1.1) 使用 len() 测量长度

len() 函数返回集合中的元素个数。它适用于字符串、列表(list)、元组(tuple)、字典(dict)、集合(set)以及任何其他集合类型。

python
# 统计字符串中的字符数
message = "Hello, World!"
print(len(message))  # Output: 13
 
# 统计列表中的元素数
scores = [85, 92, 78, 90, 88]
print(len(scores))   # Output: 5
 
# 统计字典中的键值对数量
student = {"name": "Bob", "age": 21, "major": "CS"}
print(len(student))  # Output: 3
 
# 统计集合中的唯一元素数量
unique_ids = {101, 102, 103, 101, 102}  # 重复项会被移除
print(len(unique_ids))  # Output: 3

当你在处理数据前需要知道其大小时,len() 尤其有用:

python
# 根据数据规模处理数据
data = [12, 45, 23, 67, 89, 34]
 
if len(data) < 5:
    print("Not enough data for analysis")
else:
    print(f"Analyzing {len(data)} data points")  # Output: Analyzing 6 data points
    average = sum(data) / len(data)
    print(f"Average: {average}")  # Output: Average: 45.0

37.1.2) 使用 sum() 计算总和

sum() 函数会把一个可迭代对象(iterable)中的所有数字加起来。它比编写一个循环来累加值更简洁。

python
# 对数字列表求和
prices = [19.99, 24.50, 15.75, 32.00]
total = sum(prices)
print(f"Total: ${total}")  # Output: Total: $92.24
 
# 对元组求和
daily_steps = (8500, 10200, 7800, 9500, 11000)
weekly_total = sum(daily_steps)
print(f"Total steps this week: {weekly_total}")  # Output: Total steps this week: 47000
 
# 对 range 求和
total_1_to_100 = sum(range(1, 101))
print(total_1_to_100)  # Output: 5050

一个将 sum()len() 结合来计算平均值的实用示例:

python
# 计算测试平均分
test_scores = [88, 92, 79, 85, 90, 87]
 
total_score = sum(test_scores)
num_tests = len(test_scores)
average_score = total_score / num_tests
 
print(f"Average score: {average_score:.1f}")  # Output: Average score: 86.8

重要限制sum() 只能用于数字。你不能用它来拼接字符串或合并列表:

python
# 这会引发 TypeError
words = ["Hello", " ", "World"]
# sentence = sum(words)  # TypeError: unsupported operand type(s)

37.1.3) 使用 min() 和 max() 查找极值

min()max() 函数会找到一个可迭代对象中的最小值和最大值。它们适用于数字、字符串,以及任何可比较的对象。

python
# 查找最小和最大数字
temperatures = [72, 68, 75, 70, 73, 69]
coldest = min(temperatures)
warmest = max(temperatures)
print(f"Temperature range: {coldest}°F to {warmest}°F")
# Output: Temperature range: 68°F to 75°F
 
# 查找最小和最大字符串(按字母顺序)
names = ["Zoe", "Alice", "Bob", "Charlie"]
first_alphabetically = min(names)
last_alphabetically = max(names)
print(f"First: {first_alphabetically}, Last: {last_alphabetically}")
# Output: First: Alice, Last: Zoe

你也可以直接传入多个参数,而不是一个集合:

python
# 比较单个值
lowest = min(45, 23, 67, 12, 89)
highest = max(45, 23, 67, 12, 89)
print(f"Lowest: {lowest}, Highest: {highest}")
# Output: Lowest: 12, Highest: 89
 
# 适合比较少量的特定值
price1 = 19.99
price2 = 24.50
price3 = 15.75
cheapest = min(price1, price2, price3)
print(f"Cheapest option: ${cheapest}")  # Output: Cheapest option: $15.75

37.1.4) 使用 abs() 获取绝对值

abs() 函数返回一个数字的绝对值——它到 0 的距离,始终为正。这在你关心数值大小而不关心方向时很有用。

python
# 负数的绝对值
print(abs(-42))      # Output: 42
print(abs(-3.14))    # Output: 3.14
 
# 正数的绝对值(不变)
print(abs(42))       # Output: 42
print(abs(3.14))     # Output: 3.14
 
# 0 的绝对值
print(abs(0))        # Output: 0

一个常见用法是计算方向无关的差值:

python
# 计算温度变化(只关心幅度)
morning_temp = 65
evening_temp = 72
temperature_change = abs(evening_temp - morning_temp)
print(f"Temperature changed by {temperature_change}°F")
# Output: Temperature changed by 7°F

37.1.5) 使用 round() 对数字进行四舍五入

round() 函数会把数字四舍五入到指定的小数位数。如果不传第二个参数,它会四舍五入到最接近的整数。

python
# 四舍五入到最接近的整数
print(round(3.7))     # Output: 4
print(round(3.2))     # Output: 3
print(round(3.5))     # Output: 4
print(round(4.5))     # Output: 4  (rounds to nearest even number)
 
# 四舍五入到指定小数位数
price = 19.876
print(round(price, 2))  # Output: 19.88  (2 decimal places)
print(round(price, 1))  # Output: 19.9   (1 decimal place)
 
# 四舍五入到负小数位(四舍五入到十位、百位等)
population = 1234567
print(round(population, -3))  # Output: 1235000  (nearest thousand)
print(round(population, -4))  # Output: 1230000  (nearest ten thousand)

关于刚好在中间的值的说明:当对一个恰好处于两个整数中间的数字进行舍入时,Python 有一个特殊规则。例如,2.5 恰好位于 2 和 3 的中间。你可能期望它进位到 3,但 Python 会舍入到相邻的偶数——在这个例子中是 2。

这称为“银行家舍入法”或“round half to even”。它是 IEEE 754 标准的一部分,有助于在大量舍入操作中减少偏差。

python
# 恰好在中间的值会舍入到最近的偶数
print(round(0.5))   # Output: 0 (0 is even)
print(round(1.5))   # Output: 2 (2 is even)
print(round(2.5))   # Output: 2 (2 is even)
print(round(3.5))   # Output: 4 (4 is even)
print(round(4.5))   # Output: 4 (4 is even)

37.2) 使用 enumerate() 枚举序列

在遍历序列(sequence)时,你经常既需要元素本身,也需要它的位置。enumerate() 函数同时提供两者,从而无需手动维护计数器变量。

37.2.1) 手动计数器的问题

在学习 enumerate() 之前,程序员常用计数器变量来跟踪位置:

python
# 手动计数器方式(可行但不理想)
fruits = ["apple", "banana", "cherry", "date"]
index = 0
 
for fruit in fruits:
    print(f"{index}: {fruit}")
    index += 1
# Output:
# 0: apple
# 1: banana
# 2: cherry
# 3: date

这种方式有几个缺点:

  • 需要管理额外变量(index
  • 容易忘记递增计数器

37.2.2) 使用 enumerate() 同时获取位置和值

enumerate() 函数优雅地解决了这个问题。它接受一个可迭代对象,并返回 (index, element) 这样的成对数据:

python
# 使用 enumerate() - 更简洁、更 Pythonic
fruits = ["apple", "banana", "cherry", "date"]
 
for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")
# Output:
# 0: apple
# 1: banana
# 2: cherry
# 3: date

语法 for index, fruit in enumerate(fruits) 使用了元组解包(tuple unpacking)(正如我们在第 15 章学到的)。每次迭代,enumerate() 会提供一个如 (0, "apple") 的元组,然后被解包到变量 indexfruit 中。

下面是 enumerate() 实际产生的内容:

python
# 直接查看 enumerate 的输出
fruits = ["apple", "banana", "cherry"]
enumerated = list(enumerate(fruits))
print(enumerated)
# Output: [(0, 'apple'), (1, 'banana'), (2, 'cherry')]

37.2.3) 从不同的数字开始枚举

默认情况下,enumerate() 从 0 开始计数。你可以用 start 参数指定不同的起始数字:

python
# 从 1 开始计数(用于展示很有用)
tasks = ["Write code", "Test code", "Deploy code"]
 
for number, task in enumerate(tasks, start=1):
    print(f"Step {number}: {task}")
# Output:
# Step 1: Write code
# Step 2: Test code
# Step 3: Deploy code

当向用户显示编号列表时,这尤其有用,因为用户通常期望从 1 开始计数:

python
# 带编号选项的菜单
menu_items = ["New Game", "Load Game", "Settings", "Quit"]
 
print("Main Menu:")
for number, item in enumerate(menu_items, start=1):
    print(f"{number}. {item}")
# Output:
# Main Menu:
# 1. New Game
# 2. Load Game
# 3. Settings
# 4. Quit

37.2.4) enumerate() 与字符串及其他可迭代对象

enumerate() 函数适用于任何可迭代对象,而不仅仅是列表:

python
# 枚举字符串中的字符
word = "Python"
 
for position, letter in enumerate(word):
    print(f"Letter {position}: {letter}")
# Output:
# Letter 0: P
# Letter 1: y
# Letter 2: t
# Letter 3: h
# Letter 4: o
# Letter 5: n
 
# 枚举元组
coordinates = (10, 20, 30, 40)
 
for index, value in enumerate(coordinates):
    print(f"Coordinate {index}: {value}")
# Output:
# Coordinate 0: 10
# Coordinate 1: 20
# Coordinate 2: 30
# Coordinate 3: 40

enumerate() 让代码更可读,也更不容易出错。只要你在循环(loop)中既需要位置又需要值,就应该用 enumerate(),而不是手动管理计数器。

37.3) 使用 zip() 组合序列

zip() 函数会按元素位置把多个可迭代对象逐项组合,生成成对(或元组)的对应元素。当你需要同时处理来自不同序列的相关数据时,这个函数非常有价值。

37.3.1) 理解 zip() 的工作方式

zip() 函数接受两个或更多可迭代对象,并返回一个由元组构成的迭代器(iterator),其中每个元组包含来自每个输入可迭代对象的一个元素:

python
# 组合两个列表
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
 
combined = list(zip(names, ages))
print(combined)
# Output: [('Alice', 25), ('Bob', 30), ('Charlie', 35)]

“zip” 这个名字来源于衣服上的拉链——它将两侧结构按元素逐个合并成一个整体。

下面是 zip() 如何配对元素的可视化表示:

names
['Alice', 'Bob', 'Charlie']

zip()

ages
[25, 30, 35]

[('Alice', 25),
('Bob', 30),
('Charlie', 35)]

37.3.2) 在循环中使用 zip()

zip() 最常见的用法是在 for 循环中,当你需要同时遍历多个序列时:

python
# 处理并行数据
students = ["Alice", "Bob", "Charlie", "Diana"]
scores = [92, 85, 88, 95]
 
for student, score in zip(students, scores):
    print(f"{student} scored {score}")
# Output:
# Alice scored 92
# Bob scored 85
# Charlie scored 88
# Diana scored 95

这比使用索引要简洁得多:

python
# 不使用 zip() - 更复杂且更容易出错
students = ["Alice", "Bob", "Charlie", "Diana"]
scores = [92, 85, 88, 95]
 
for i in range(len(students)):
    print(f"{students[i]} scored {scores[i]}")
# Same output, but more code and potential for index errors

37.3.3) 处理长度不同的序列

当输入序列长度不同时,zip() 会在最短序列耗尽时停止:

python
# 长度不相等的序列
names = ["Alice", "Bob", "Charlie", "Diana"]
ages = [25, 30]  # Only 2 ages
 
for name, age in zip(names, ages):
    print(f"{name} is {age} years old")
# Output:
# Alice is 25 years old
# Bob is 30 years old
# (Charlie and Diana are not processed)

这种行为可以防止错误,但如果你不小心,可能会导致静默的数据丢失。务必验证你的序列长度是否符合预期:

python
# 检查长度不匹配
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30]
 
if len(names) != len(ages):
    print(f"Warning: {len(names)} names but {len(ages)} ages")
    print("Only processing the first", min(len(names), len(ages)), "entries")
    # Output: Warning: 3 names but 2 ages
    # Output: Only processing the first 2 entries
 
# 继续使用 zip() - 它会在最短序列处停止
for name, age in zip(names, ages):
    print(f"{name} is {age} years old")

37.3.4) 压缩两个以上的序列

zip() 函数可以组合任意数量的可迭代对象:

python
# 组合三个序列
products = ["Laptop", "Mouse", "Keyboard"]
prices = [999.99, 24.99, 79.99]
quantities = [5, 20, 15]
 
print("Inventory Report:")
for product, price, quantity in zip(products, prices, quantities):
    total_value = price * quantity
    print(f"{product}: ${price} × {quantity} = ${total_value:.2f}")
# Output:
# Inventory Report:
# Laptop: $999.99 × 5 = $4999.95
# Mouse: $24.99 × 20 = $499.80
# Keyboard: $79.99 × 15 = $1199.85

37.3.5) 使用 zip() 创建字典

一个强大的模式是用 zip() 从分别的键和值序列创建字典:

python
# 从两个列表创建字典
keys = ["name", "age", "city"]
values = ["Alice", 25, "Boston"]
 
person = dict(zip(keys, values))
print(person)
# Output: {'name': 'Alice', 'age': 25, 'city': 'Boston'}

37.4) 使用 any() 和 all() 进行布尔聚合

any()all() 函数会在整个可迭代对象上测试条件,返回单个布尔值(boolean)结果。它们是基于多个条件进行验证与决策的强大工具。

37.4.1) 理解 any():只要至少一个元素为 True 就返回 True

any() 函数在可迭代对象中只要至少有一个元素为真值(truthy)(求值为 True),就返回 True。如果所有元素都为假值(falsy),则返回 False

python
# any() 基本示例
print(any([True, False, False]))   # Output: True  (at least one True)
print(any([False, False, False]))  # Output: False (all False)
print(any([False, True, True]))    # Output: True  (multiple True values)
 
# 空可迭代对象
print(any([]))  # Output: False (no elements to be True)

any() 使用 Python 的真值规则(正如我们在第 7 章学到的)。非零数字、非空字符串、非空集合都是真值:

python
# any() 与不同的 truthy/falsy 值
print(any([0, 0, 1]))           # Output: True  (1 is truthy)
print(any([0, 0, 0]))           # Output: False (all zeros are falsy)
print(any(["", "", "text"]))    # Output: True  ("text" is truthy)
print(any(["", "", ""]))        # Output: False (empty strings are falsy)

37.4.2) any() 的实用用法

示例:检查是否满足任意一个条件

python
# 检查是否存在不及格分数(低于 60)
scores = [75, 82, 55, 90, 88]
has_failing_grade = any(score < 60 for score in scores)
 
if has_failing_grade:
    print("Warning: At least one failing grade")
    # Output: Warning: At least one failing grade
else:
    print("All grades are passing")

37.4.3) 理解 all():只有全部元素为 True 才返回 True

all() 函数只有在可迭代对象中所有元素都为真值(truthy)时才返回 True。只要有任意一个元素为假值(falsy),它就返回 False

python
# all() 基本示例
print(all([True, True, True]))    # Output: True  (all True)
print(all([True, False, True]))   # Output: False (one False)
print(all([True, True, False]))   # Output: False (one False)
 
# 空可迭代对象
print(all([]))  # Output: True (vacuous truth - no False elements)

对于空可迭代对象的行为可能看起来令人意外:all([]) 返回 True。这称为空真(vacuous truth)——当没有任何元素反驳时,“所有元素都为 True” 这个说法在技术上确实为真。

python
# all() 与不同的 truthy/falsy 值
print(all([1, 2, 3]))           # Output: True  (all non-zero)
print(all([1, 0, 3]))           # Output: False (0 is falsy)
print(all(["a", "b", "c"]))     # Output: True  (all non-empty)
print(all(["a", "", "c"]))      # Output: False (empty string is falsy)

37.4.4) all() 的实用用法

示例:验证是否满足所有条件

python
# 检查是否所有分数都及格(60 或以上)
scores = [75, 82, 68, 90, 88]
all_passing = all(score >= 60 for score in scores)
 
if all_passing:
    print("Congratulations! All grades are passing")
    # Output: Congratulations! All grades are passing
else:
    print("Some grades need improvement")

37.4.5) any() 与 all() 中的短路求值

any()all() 都使用短路求值(short-circuit evaluation)(正如我们在第 9 章学到的)。一旦结果已经确定,它们就会停止检查:

python
# 被调用时打印信息的函数(用于展示执行过程)
def is_positive(n):
    print(f"Checking {n}")
    return n > 0
 
# any() 在遇到第一个 True 时停止
print("Testing any():")
numbers = [0, 0, 1, 2, 3]
result = any(is_positive(n) for n in numbers)
# Output:
# Testing any():
# Checking 0
# Checking 0
# Checking 1
# (Stops here - doesn't check 2 or 3)
print(f"Result: {result}")  # Output: Result: True
 
print("\nTesting all():")
numbers = [1, 2, 0, 3, 4]
result = all(is_positive(n) for n in numbers)
# Output:
# Testing all():
# Checking 1
# Checking 2
# Checking 0
# (Stops here - doesn't check 3 or 4)
print(f"Result: {result}")  # Output: Result: False

这使得 any()all() 很高效——它们不会在结果确定之后浪费时间继续检查元素。

37.5) 使用 sorted() 与自定义键进行排序

sorted() 函数会从任意可迭代对象创建一个新的已排序列表(list)。与 .sort() 方法(仅适用于列表并原地修改)不同,sorted() 适用于任何可迭代对象,并且总是返回一个新列表。

37.5.1) 使用 sorted() 进行基础排序

sorted() 函数默认按升序排列元素:

python
# 对数字排序
numbers = [42, 17, 93, 8, 55]
sorted_numbers = sorted(numbers)
print(sorted_numbers)  # Output: [8, 17, 42, 55, 93]
 
# 原始列表不变
print(numbers)  # Output: [42, 17, 93, 8, 55]
 
# 对字符串排序(按字母顺序)
names = ["Charlie", "Alice", "Bob", "Diana"]
sorted_names = sorted(names)
print(sorted_names)  # Output: ['Alice', 'Bob', 'Charlie', 'Diana']

sorted() 适用于任何可迭代对象,而不仅仅是列表:

python
# 对元组排序(返回列表)
coordinates = (5, 2, 8, 1, 9)
sorted_coords = sorted(coordinates)
print(sorted_coords)  # Output: [1, 2, 5, 8, 9]
 
# 对字符串排序(返回字符列表)
word = "python"
sorted_letters = sorted(word)
print(sorted_letters)  # Output: ['h', 'n', 'o', 'p', 't', 'y']
 
# 对集合排序(返回已排序列表)
unique_numbers = {5, 8, 2, 1}
sorted_unique = sorted(unique_numbers)
print(sorted_unique)  # Output: [1, 2, 5, 8]

37.5.2) 反向排序

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

python
# 数字降序
scores = [85, 92, 78, 95, 88]
highest_first = sorted(scores, reverse=True)
print(highest_first)  # Output: [95, 92, 88, 85, 78]
 
# 字符串降序(反字母顺序)
names = ["Charlie", "Alice", "Bob", "Diana"]
reverse_alpha = sorted(names, reverse=True)
print(reverse_alpha)  # Output: ['Diana', 'Charlie', 'Bob', 'Alice']

37.5.3) 理解 key 参数

key 参数是 sorted() 真正强大的地方。它改变了 Python 在排序时比较元素的方式。

什么是 key 参数?

key 参数接受一个函数(function)。Python 会对每个元素调用该函数以提取“比较键(comparison key)”,然后根据这些键进行排序,而不是根据原始元素排序。

它如何逐步工作:

  1. Python 对每个元素调用 key 函数
  2. Python 收集所有键
  3. Python 通过比较这些键来排序
  4. Python 按新顺序返回原始元素
python
# 示例:按长度排序
words = ["python", "is", "awesome"]
 
# Step 1: Python calls len() on each word
# len("python")  → 6
# len("is")      → 2
# len("awesome") → 7
 
# Step 2: Python has these keys: [6, 2, 7]
 
# Step 3: Python sorts the keys: [2, 6, 7]
 
# Step 4: Python returns words in that order: ["is", "python", "awesome"]
 
result = sorted(words, key=len)
print(result)  # Output: ['is', 'python', 'awesome']

将 key 函数可视化:

原始:
['python', 'is', 'awesome']

应用 key=len

键:
[6, 2, 7]

排序键

已排序的键:
[2, 6, 7]

返回元素:
['is', 'python', 'awesome']

key 函数可以是什么?

key 函数必须:

  • 接受一个参数(被排序的元素)
  • 返回一个 Python 可比较的值(数字、字符串、元组等)
python
# 内置函数非常适合作为 key 函数
sorted(numbers, key=abs)        # 按绝对值排序
sorted(words, key=len)          # 按长度排序
sorted(names, key=str.lower)    # 不区分大小写排序
 
# 你自己的函数
def first_letter(word):
    return word[0]
 
sorted(words, key=first_letter)  # 按首字母排序
 
# lambda 函数(第 23 章)
sorted(words, key=lambda w: w[-1])  # 按最后一个字母排序

重要:key 函数对每个元素只调用一次

python
# 演示 key 函数在何时被调用
def show_key(word):
    print(f"Getting key for: {word}")
    return len(word)
 
words = ["cat", "elephant", "dog"]
result = sorted(words, key=show_key)
# Output:
# Getting key for: cat
# Getting key for: elephant
# Getting key for: dog
 
print(result)  # Output: ['cat', 'dog', 'elephant']

重要:key 函数对每个元素只调用一次

注意,show_key 对每个单词恰好只调用一次,而不是在比较期间反复调用。Python 很高效——它会先提取所有键、缓存它们,然后使用缓存的键进行排序。

把 key 看作在回答:“我应该比较哪个方面?”

  • key=len → “按长度比较”
  • key=abs → “按绝对值比较”
  • key=str.lower → “按全部转为小写后的结果比较”
  • key=lambda x: x[1] → “按第二个元素比较”

key 参数让你可以按元素的任意属性进行排序,使 sorted() 极其通用。

37.5.4) 使用内置函数作为 key 进行排序

Python 的内置函数非常适合作为 key 函数:

python
# 按绝对值排序
numbers = [-5, 2, -8, 1, -3, 7]
sorted_by_magnitude = sorted(numbers, key=abs)
print(sorted_by_magnitude)  # Output: [1, 2, -3, -5, 7, -8]
 
# 字符串不区分大小写排序
names = ["alice", "Bob", "CHARLIE", "diana"]
sorted_case_insensitive = sorted(names, key=str.lower)
print(sorted_case_insensitive)  # Output: ['alice', 'Bob', 'CHARLIE', 'diana']

37.5.5) 排序复杂数据结构

当排序由元组或列表组成的列表时,你可以用索引来指定按哪个元素排序:

python
# 按元组的第二个元素排序
students = [
    ("Alice", 92),
    ("Bob", 85),
    ("Charlie", 88),
    ("Diana", 95)
]
 
# 按分数排序(第二个元素)
by_score = sorted(students, key=lambda student: student[1])
print(by_score)
# Output: [('Bob', 85), ('Charlie', 88), ('Alice', 92), ('Diana', 95)]
 
# 按分数降序排序
by_score_desc = sorted(students, key=lambda student: student[1], reverse=True)
print(by_score_desc)
# Output: [('Diana', 95), ('Alice', 92), ('Charlie', 88), ('Bob', 85)]

注意:这里我们使用了 lambda(正如我们在第 23 章学到的)。lambda 是一种小型匿名函数。表达式 lambda student: student[1] 创建了一个函数,它接收一个学生元组并返回其第二个元素(分数)。

37.5.6) 多级排序

你可以通过让 key 函数返回一个元组来按多个条件排序。Python 会从左到右逐个比较元组的元素:

元组比较如何工作:

当 Python 比较两个元组时,会遵循以下规则:

  1. 先比较第一个元素。如果不同,比较结束。
  2. 如果第一个元素相同,比较第二个元素。
  3. 如果第二个元素相同,比较第三个元素。
  4. 持续下去,直到找到差异或元素用完。
python
# 元组比较示例
print((1, 'a') < (2, 'z'))   # Output: True  (1 < 2, so True immediately)
print((1, 'z') < (1, 'a'))   # Output: False (1 == 1, so compare 'z' < 'a')
print((1, 'a') < (1, 'a'))   # Output: False (both tuples are equal)
print((1, 2, 9) < (1, 3, 1)) # Output: True  (1 == 1, then 2 < 3)

这使得元组非常适合多级排序——Python 会自动为你处理“先比较第一条件,再比较第二条件,再比较第三条件”的逻辑:

python
# 按多个条件排序
students = [
    ("Alice", "Smith", 92),
    ("Bob", "Jones", 85),
    ("Alice", "Brown", 88),
    ("Charlie", "Smith", 85)
]
 
# 先按名,再按姓排序
by_name = sorted(students, key=lambda s: (s[0], s[1]))
print("By name:")
for student in by_name:
    print(f"  {student}")
# Output:
# By name:
#   ('Alice', 'Brown', 88)
#   ('Alice', 'Smith', 92)
#   ('Bob', 'Jones', 85)
#   ('Charlie', 'Smith', 85)
 
# 先按分数降序,再按名字升序
by_score_then_name = sorted(students, key=lambda s: (-s[2], s[0]))
print("\nBy score (high to low), then name:")
for student in by_score_then_name:
    print(f"  {student}")
# Output:
# By score (high to low), then name:
#   ('Alice', 'Smith', 92)
#   ('Alice', 'Brown', 88)
#   ('Bob', 'Jones', 85)
#   ('Charlie', 'Smith', 85)

注意:要实现一个条件降序、另一个条件升序,我们对数值取负(-s[2])。这是可行的,因为取负会反转数字的排序顺序。在上面的例子中,-s[2] 会让分数从高到低排序,而 s[0] 会让名字从 A 到 Z 排序。

37.5.7) 为复杂 key 使用辅助函数

当排序逻辑变得复杂时,定义一个辅助函数会让代码更可读、更易维护。然后你可以在 key 函数中使用这个辅助函数。

示例:按扩展名排序文件

假设你想按文件扩展名(.csv.jpg.pdf 等)对文件分组,并在每个分组内按文件名的字母顺序排序。key 函数需要提取扩展名,这需要一些字符串处理。

python
# 先按扩展名排序,再按名称排序
files = [
    "report.pdf",
    "data.csv",
    "image.jpg",
    "notes.txt",
    "backup.csv",
    "photo.jpg"
]
 
# 提取扩展名用于排序
def get_extension(filename):
    """从文件名中提取文件扩展名。"""
    return filename.split(".")[-1]  # 按 "." 分割并取最后一部分
 
# 在 key 中使用辅助函数
sorted_files = sorted(files, key=lambda f: (get_extension(f), f))
print("Files sorted by extension, then name:")
for file in sorted_files:
    print(f"  {file}")
# Output:
# Files sorted by extension, then name:
#   backup.csv      # csv files first (alphabetically)
#   data.csv        # csv files first (alphabetically)
#   image.jpg       # jpg files next
#   photo.jpg       # jpg files next
#   report.pdf      # pdf files next
#   notes.txt       # txt files last

它如何工作:

  1. key 函数 lambda f: (get_extension(f), f) 为每个文件名返回一个元组
  2. 对于 "report.pdf",它返回 ("pdf", "report.pdf")
  3. 对于 "data.csv",它返回 ("csv", "data.csv")
  4. Python 先按元组的第一个元素(扩展名)排序,再按第二个元素(完整文件名)排序
  5. 这样就会按扩展名分组,并在每组内按字母顺序排序

为什么要用辅助函数?

对比可读性:

python
# 不使用辅助函数 - 更难理解
sorted_files = sorted(files, key=lambda f: (f.split(".")[-1], f))
 
# 使用辅助函数 - 意图更清晰
sorted_files = sorted(files, key=lambda f: (get_extension(f), f))

辅助函数让你的代码自解释。任何人看到 get_extension(f) 都能立刻明白发生了什么,而 f.split(".")[-1] 则需要在脑中进行解析。

37.5.8) sorted() vs .sort():何时使用哪个

Python 提供两种排序方式:

  1. sorted() - 返回一个新排序列表的函数
  2. .sort() - 原地排序的列表方法
python
# sorted() - 创建新列表,原始列表不变
numbers = [3, 1, 4, 1, 5]
sorted_numbers = sorted(numbers)
print(f"Original: {numbers}")        # Output: Original: [3, 1, 4, 1, 5]
print(f"Sorted: {sorted_numbers}")   # Output: Sorted: [1, 1, 3, 4, 5]
 
# .sort() - 原地修改列表,返回 None
numbers = [3, 1, 4, 1, 5]
result = numbers.sort()
print(f"Modified: {numbers}")        # Output: Modified: [1, 1, 3, 4, 5]
print(f"Return value: {result}")     # Output: Return value: None

何时使用 sorted()

  • 你需要保留原始顺序
  • 你要排序的对象不是列表(元组、字符串、集合等)
  • 你想在一个表达式里完成排序并赋值

何时使用 .sort()

  • 你有一个列表并且不需要保留原始顺序
  • 你想节省内存(不创建新列表)
  • 你需要为效率原地排序一个大列表

sorted() 函数是 Python 最通用的工具之一。结合 key 参数,它几乎可以处理任何排序需求:从简单的数字排序,到复杂的多条件排序,再到对嵌套数据结构进行排序。


本章为你配备了 Python 的关键内置函数与工具。你已经学会了如何:

  • 理解 Python 的类型层级,并预测哪些操作适用于哪些类型
  • 使用 len()sum()min()max()abs()round() 等基础函数完成常见操作
  • 使用 enumerate() 在迭代时获取位置信息
  • 使用 zip() 同时处理并行序列
  • 使用 any()all() 在集合上做整体决策
  • 使用 sorted() 与自定义 key 函数灵活排序数据

这些工具构成了地道(idiomatic) Python 代码的基础。它们高效、可读,并能正确处理边界情况。随着你继续编程,你会发现自己会不断使用这些函数——它们是让 Python 代码优雅且富有表达力的构建块。

在下一章中,我们将探索装饰器(decorators),它能让你以强大的方式修改和增强函数行为,并在我们第 23 章学到的一等函数(first-class function)概念之上继续构建。

© 2025. Primesoft Co., Ltd.
support@primesoft.ai