Administrator
发布于 2026-03-19 / 2 阅读
0
0

第六章:列表、字典、集合

第六章:列表、字典、集合

本章学习目标

  • 掌握列表的常用操作和方法
  • 理解字典的键值对操作
  • 学会使用集合进行去重和集合运算
  • 熟练使用列表推导式、字典推导式和生成器表达式
  • 理解可变对象与不可变对象的区别

6.1 列表 (list)

6.1.1 列表简介

列表是 Python 中最常用、最灵活的数据结构之一。列表可以存储有序的元素序列,并且可以包含不同类型的元素。列表是可变对象,这意味着我们可以在原地修改列表的内容。

# 创建列表
numbers: list[int] = [1, 2, 3, 4, 5]          # 整数列表
empty: list[str] = []                          # 空列表
mixed: list[int | str] = [1, "a", 2, "b"]     # 混合类型列表

# 使用 list() 构造函数
from_iterable: list[str] = list("hello")       # ['h', 'e', 'l', 'l', 'o']
from_range: list[int] = list(range(10))        # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 列表推导式创建
squares: list[int] = [x ** 2 for x in range(10)]  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

6.1.2 列表索引和切片

列表中的每个元素都有一个唯一的索引,索引从 0 开始。我们可以通过索引来访问列表中的元素。

fruits: list[str] = ["apple", "banana", "cherry", "date", "elderberry"]

# 索引访问
print(fruits[0])    # apple (第一个元素)
print(fruits[1])    # banana (第二个元素)
print(fruits[-1])    # elderberry (最后一个元素)
print(fruits[-2])    # date (倒数第二个元素)

# 索引越界会抛出 IndexError
# print(fruits[10])  # IndexError: list index out of range

# 切片操作 [start:stop:step]
print(fruits[1:4])    # ['banana', 'cherry', 'date'] (索引1到3)
print(fruits[:3])     # ['apple', 'banana', 'cherry'] (从头到索引2)
print(fruits[2:])     # ['cherry', 'date', 'elderberry'] (从索引2到末尾)
print(fruits[::2])    # ['apple', 'cherry', 'elderberry'] (步长2)
print(fruits[::-1])   # ['elderberry', 'date', 'cherry', 'banana', 'apple'] (反转)

# 切片越界不会报错
print(fruits[1:100])   # ['banana', 'cherry', 'date', 'elderberry']
print(fruits[100:])    # []

6.1.3 修改列表

列表是可变对象,我们可以直接修改列表中的元素,或者添加、删除元素。

numbers: list[int] = [1, 2, 3, 4, 5]

# 修改元素
numbers[0] = 10
print(numbers)  # [10, 2, 3, 4, 5]

# 添加元素
numbers.append(6)          # 在末尾添加单个元素
print(numbers)  # [10, 2, 3, 4, 5, 6]

numbers.insert(0, 0)       # 在指定位置插入元素
print(numbers)  # [0, 10, 2, 3, 4, 5, 6]

numbers.extend([7, 8, 9])  # 在末尾添加多个元素
print(numbers)  # [0, 10, 2, 3, 4, 5, 6, 7, 8, 9]

# 删除元素
numbers.remove(10)         # 删除第一个匹配的元素
print(numbers)  # [0, 2, 3, 4, 5, 6, 7, 8, 9]

popped: int = numbers.pop()    # 弹出并返回最后一个元素
print(popped)  # 9
print(numbers)  # [0, 2, 3, 4, 5, 6, 7, 8]

numbers.pop(0)         # 弹出指定位置的元素
print(numbers)  # [2, 3, 4, 5, 6, 7, 8]

del numbers[0]         # 删除指定位置的元素
print(numbers)  # [3, 4, 5, 6, 7, 8]

numbers.clear()       # 清空列表
print(numbers)  # []

6.1.4 列表方法详解

# 查找方法
fruits: list[str] = ["apple", "banana", "cherry", "banana"]

print(fruits.index("cherry"))       # 2 (返回元素第一次出现的索引)
print(fruits.count("banana"))       # 2 (返回元素出现的次数)

# 排序方法
numbers: list[int] = [3, 1, 4, 1, 5, 9, 2, 6]
numbers.sort()                      # 原地排序
print(numbers)  # [1, 1, 2, 3, 4, 5, 6, 9]

numbers.sort(reverse=True)          # 降序排序
print(numbers)  # [9, 6, 5, 4, 3, 2, 1, 1]

# 使用 key 参数进行自定义排序
words: list[str] = ["banana", "Apple", "cherry"]
words.sort()                         # 默认按 ASCII 顺序
print(words)  # ['Apple', 'banana', 'cherry']

words.sort(key=str.lower)           # 忽略大小写排序
print(words)  # ['Apple', 'banana', 'cherry']

# 返回新列表(不修改原列表)
sorted_nums: list[int] = sorted(numbers, reverse=True)

# 逆序
numbers.reverse()                   # 原地逆序
print(numbers)  # [1, 1, 2, 3, 4, 5, 6, 9]

# 复制列表
original: list[int] = [1, 2, 3]
shallow_copy: list[int] = original.copy()      # 方法1
shallow_copy2: list[int] = original[:]       # 方法2(切片)
shallow_copy3: list[int] = list(original)    # 方法3

# 注意:浅拷贝只复制第一层
nested: list[list[int]] = [[1, 2], [3, 4]]
copied: list[list[int]] = nested.copy()
copied[0][0] = 100
print(nested)  # [[100, 2], [3, 4]] (原列表也被修改)

6.1.5 列表解包

Python 允许将列表(或任何可迭代对象)解包为多个变量。

# 基本解包
first, second, third = [1, 2, 3]
print(first, second, third)  # 1 2 3

# 使用 * 解包剩余元素
first, *middle, last = [1, 2, 3, 4, 5]
print(first)   # 1
print(middle)  # [2, 3, 4]
print(last)    # 5

# 交换变量(无需临时变量)
a, b = 1, 2
a, b = b, a
print(f"a={a}, b={b}")  # a=2, b=1

# 在函数参数中使用
def greet(name, greeting):
    print(f"{greeting}, {name}!")

args = ["Alice", "Hello"]
greet(*args)  # Hello, Alice!

# zip 解包
pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
numbers, letters = zip(*pairs)
print(numbers)  # (1, 2, 3)
print(letters)  # ('a', 'b', 'c')

6.1.6 列表推导式

列表推导式是一种简洁的创建列表的方式,类似于数学中的集合表示法。

# 基本语法: [expression for item in iterable]

# 生成 0-9 的平方
squares: list[int] = [x ** 2 for x in range(10)]
print(squares)  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# 带条件过滤
evens: list[int] = [x for x in range(10) if x % 2 == 0]
print(evens)  # [0, 2, 4, 6, 8]

# 带转换
words: list[str] = ["hello", "world", "python"]
upper_words: list[str] = [w.upper() for w in words]
print(upper_words)  # ['HELLO', 'WORLD', 'PYTHON']

# 同时使用 if-else(三元表达式)
# 语法: [expr_if_true if condition else expr_if_false for item in iterable]
numbers: list[int] = [1, 2, 3, 4, 5]
labels: list[str] = ["偶数" if x % 2 == 0 else "奇数" for x in numbers]
print(labels)  # ['奇数', '偶数', '奇数', '偶数', '奇数']

# 多层条件
result: list[int] = [x for x in range(100) if x % 2 == 0 if x % 3 == 0]
print(result)  # [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96]

# 嵌套列表推导式
matrix: list[list[int]] = [[i * j for j in range(3)] for i in range(3)]
print(matrix)
# [[0, 0, 0],
#  [0, 1, 2],
#  [0, 2, 4]]

# 扁平化嵌套列表
nested: list[list[int]] = [[1, 2], [3, 4], [5, 6]]
flat: list[int] = [num for row in nested for num in row]
print(flat)  # [1, 2, 3, 4, 5, 6]

6.1.7 列表内存模型

理解列表的内存模型对于避免常见错误很重要。

# 列表引用 vs 复制
a: list[int] = [1, 2, 3]
b = a           # b 指向同一个列表
b.append(4)
print(a)        # [1, 2, 3, 4] (a 也被修改)

# 正确复制
a: list[int] = [1, 2, 3]
b = a.copy()   # b 是新的列表
b.append(4)
print(a)        # [1, 2, 3] (a 不变)

# 嵌套列表的深拷贝
import copy

nested: list[list[int]] = [[1, 2], [3, 4]]
deep_copied: list[list[int]] = copy.deepcopy(nested)

6.2 字典 (dict)

6.2.1 字典简介

字典是 Python 中存储键值对映射的数据结构。字典提供快速的查找性能(平均时间复杂度为 O(1)),因为它使用哈希表实现。

# 创建字典
person: dict[str, str | int] = {
    "name": "Alice",
    "age": 25,
    "city": "Beijing"
}

# 使用 dict() 构造函数
user: dict[str, int] = dict(name=25, age=30)  # 错误用法
user = dict(name="Bob", age=30)  # {'name': 'Bob', 'age': 30}

# 从键值对列表创建
pairs: list[tuple[str, int]] = [("a", 1), ("b", 2)]
d: dict[str, int] = dict(pairs)

# 字典推导式创建
squares: dict[int, int] = {x: x ** 2 for x in range(5)}
print(squares)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

6.2.2 访问字典

person: dict[str, str | int] = {"name": "Alice", "age": 25}

# 使用键访问(键不存在时抛出 KeyError)
name: str = person["name"]
# person["gender"]  # KeyError: 'gender'

# 使用 get 方法(更安全)
gender: str | None = person.get("gender")       # None (默认值)
gender: str = person.get("gender", "unknown")  # "unknown" (自定义默认值)

# 修改/添加
person["email"] = "alice@example.com"
person["age"] = 26

# 使用 update 批量更新
person.update({"age": 27, "country": "China"})

6.2.3 删除操作

person: dict[str, str | int] = {"name": "Alice", "age": 25, "city": "Beijing", "email": "alice@example.com"}

# 使用 del 删除
del person["email"]
print(person)  # {'name': 'Alice', 'age': 25, 'city': 'Beijing'}

# 使用 pop 删除并返回值
age: int = person.pop("age")
print(age)     # 25
print(person)  # {'name': 'Alice', 'city': 'Beijing'}

# pop 支持默认值(键不存在时返回默认值)
gender: str = person.pop("gender", "unknown")

# 使用 popitem 删除最后一项(Python 3.7+)
item: tuple[str, str | int] = person.popitem()
print(item)    # ('city', 'Beijing')

# 清空字典
person.clear()
print(person)  # {}

6.2.4 字典方法详解

data: dict[str, int] = {"a": 1, "b": 2, "c": 3}

# keys(): 返回所有键
keys: dict_keys[str] = data.keys()
print(list(keys))  # ['a', 'b', 'c']

# values(): 返回所有值
values: dict_values[int] = data.values()
print(list(values))  # [1, 2, 3]

# items(): 返回所有键值对
items: dict_items[tuple[str, int]] = data.items()
print(list(items))  # [('a', 1), ('b', 2), ('c', 3)]

# 遍历字典
for key in data:
    print(key)

for value in data.values():
    print(value)

for key, value in data.items():
    print(f"{key}: {value}")

# setdefault: 获取值,不存在时设置默认值
d: dict[str, int] = {"a": 1}
d.setdefault("b", 2)    # 返回 2,d = {'a': 1, 'b': 2}
d.setdefault("a", 100) # 返回已存在的值 1,d 不变

6.2.5 字典推导式

# 交换键值
original: dict[str, int] = {"a": 1, "b": 2, "c": 3}
swapped: dict[int, str] = {v: k for k, v in original.items()}
print(swapped)  # {1: 'a', 2: 'b', 3: 'c'}

# 过滤字典
scores: dict[str, int] = {"Alice": 85, "Bob": 92, "Charlie": 78, "Diana": 90}
passing: dict[str, int] = {k: v for k, v in scores.items() if v >= 80}
print(passing)  # {'Bob': 92, 'Diana': 90}

# 转换值
data: dict[str, int] = {"a": 1, "b": 2, "c": 3}
doubled: dict[str, int] = {k: v * 2 for k, v in data.items()}
print(doubled)  # {'a': 2, 'b': 4, 'c': 6}

# 根据条件分组
names: list[str] = ["Alice", "Bob", "Anna", "Bill", "Ben"]
grouped: dict[str, list[str]] = {}
for name in names:
    key = name[0]
    if key not in grouped:
        grouped[key] = []
    grouped[key].append(name)

print(grouped)  # {'A': ['Alice', 'Anna'], 'B': ['Bob', 'Bill', 'Ben']}

# 使用字典推导式的分组(更简洁)
grouped_v2: dict[str, list[str]] = {
    name[0]: [n for n in names if n[0] == name[0]]
    for name in names
}

6.2.6 defaultdict

defaultdict 是 dict 的子类,当访问不存在的键时,会自动创建一个默认值。

from collections import defaultdict

# 使用 int 作为默认值(计数器)
counter: defaultdict[str, int] = defaultdict(int)
words: list[str] = ["apple", "banana", "apple", "cherry", "banana", "apple"]

for word in words:
    counter[word] += 1

print(dict(counter))  # {'apple': 3, 'banana': 2, 'cherry': 1}

# 使用 list 作为默认值(分组)
grouped: defaultdict[str, list[str]] = defaultdict(list)
names: list[str] = ["Alice", "Bob", "Charlie", "Anna", "Brian"]

for name in names:
    grouped[name[0]].append(name)

print(dict(grouped))
# {'A': ['Alice', 'Anna'], 'B': ['Bob', 'Brian'], 'C': ['Charlie']}

# 使用 dict 作为默认值(嵌套字典)
nested: defaultdict[str, dict[str, int]] = defaultdict(dict)
nested["person"]["age"] = 25
print(dict(nested))  # {'person': {'age': 25}}

# 使用 lambda 自定义默认值
custom: defaultdict[str, list[int]] = defaultdict(lambda: [0, 0])
custom["score"].append(100)
print(dict(custom))  # {'score': [0, 0, 100]}

6.2.7 字典视图

字典的 keys()、values()、items() 返回的是视图对象,它们是动态的,会反映字典的变化。

d: dict[str, int] = {"a": 1, "b": 2}

# 视图对象
keys_view = d.keys()
values_view = d.values()
items_view = d.items()

# 动态更新
d["c"] = 3

# 视图会反映变化
print(list(keys_view))  # ['a', 'b', 'c']

# 集合操作(keys 支持)
keys: set[str] = set(d.keys())

6.3 集合 (set)

6.3.1 集合简介

集合是无序且不重复的元素集合。集合中的元素必须是可哈希的(hashable),即不可变类型。集合主要用于成员检查、去重和集合运算。

# 创建集合
fruits: set[str] = {"apple", "banana", "cherry"}
empty: set[int] = set()  # 注意:不能用 {},{} 创建的是空字典

# 从列表创建(自动去重)
unique: set[int] = set([1, 2, 2, 3, 3, 3])
print(unique)  # {1, 2, 3}

# 集合推导式
squares: set[int] = {x ** 2 for x in range(10)}
print(squares)  # {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}

6.3.2 添加和删除元素

s: set[str] = {"apple", "banana"}

# 添加单个元素
s.add("cherry")
print(s)  # {'apple', 'banana', 'cherry'}

# 添加多个元素
s.update(["orange", "grape", "melon"])
print(s)  # {'apple', 'banana', 'cherry', 'grape', 'melon', 'orange'}

# 删除元素
s.remove("banana")    # 元素不存在会抛出 KeyError
# s.remove("not_exist")  # KeyError

s.discard("apple")    # 元素不存在不会报错
s.discard("not_exist") # 静默失败

# 随机弹出一个元素
popped: str | None = s.pop()
print(popped)

# 清空集合
s.clear()

6.3.3 集合运算

集合支持数学上的集合运算:并集、交集、差集、对称差集。

a: set[int] = {1, 2, 3, 4, 5}
b: set[int] = {4, 5, 6, 7, 8}

# 并集 (Union)
union: set[int] = a | b
union = a.union(b)
print(union)  # {1, 2, 3, 4, 5, 6, 7, 8}

# 交集 (Intersection)
intersection: set[int] = a & b
intersection = a.intersection(b)
print(intersection)  # {4, 5}

# 差集 (Difference)
difference: set[int] = a - b
difference = a.difference(b)
print(difference)  # {1, 2, 3}

# 对称差集 (Symmetric Difference)
sym_diff: set[int] = a ^ b
sym_diff = a.symmetric_difference(b)
print(sym_diff)  # {1, 2, 3, 6, 7, 8}

# 运算并赋值
a_copy: set[int] = a.copy()
a_copy |= {9, 10}     # 并集赋值
a_copy &= b           # 交集赋值
a_copy -= b           # 差集赋值
a_copy ^= b           # 对称差集赋值

6.3.4 集合关系判断

x: set[int] = {1, 2, 3}
y: set[int] = {1, 2, 3, 4, 5}
z: set[int] = {4, 5, 6}

# 判断子集
x.issubset(y)     # True (x 是 y 的子集)
y.issuperset(x)   # True (y 是 x 的父集)

# 判断真子集/真父集
x < y             # True (x 是 y 的真子集)
y > x             # True (y 是 x 的真父集)

# 判断是否不相交
x.isdisjoint(z)   # True (x 和 z 没有交集)

6.3.5 frozenset(不可变集合)

frozenset 是不可变的集合,可以作为字典的键或集合的元素。

# 创建 frozenset
fs: frozenset[int] = frozenset([1, 2, 3])
print(fs)  # frozenset({1, 2, 3})

# frozenset 作为字典的键
d: dict[frozenset[str], str] = {
    frozenset(["a", "b"]): "AB",
    frozenset(["a", "c"]): "AC"
}
print(d)  # {frozenset({'a', 'b'}): 'AB', frozenset({'a', 'c'}): 'AC'}

6.4 综合示例

示例 1:统计词频

def word_frequency(text: str) -> dict[str, int]:
    """统计文本中每个词的出现频率"""
    words: list[str] = text.lower().split()
    freq: dict[str, int] = {}
    for word in words:
        freq[word] = freq.get(word, 0) + 1
    return freq

text: str = "hello world hello python world"
result: dict[str, int] = word_frequency(text)
print(result)  # {'hello': 2, 'world': 2, 'python': 1}

# 使用 defaultdict 简化
def word_frequency_v2(text: str) -> dict[str, int]:
    from collections import defaultdict
    words = text.lower().split()
    freq: defaultdict[str, int] = defaultdict(int)
    for word in words:
        freq[word] += 1
    return dict(freq)

示例 2:列表去重保持顺序

def unique_items(items: list[int]) -> list[int]:
    """列表去重并保持原有顺序"""
    seen: set[int] = set()
    result: list[int] = []
    for item in items:
        if item not in seen:
            seen.add(item)
            result.append(item)
    return result

# 测试
original: list[int] = [1, 2, 2, 3, 1, 4, 3, 5]
print(unique_items(original))  # [1, 2, 3, 4, 5]

示例 3:多重分组

from collections import defaultdict

def group_by_multiple_keys(
    items: list[dict[str, str | int]],
    *keys: str
) -> dict[tuple, list[dict[str, str | int]]]:
    """根据多个键对列表进行分组"""
    grouped: defaultdict[tuple, list[dict[str, str | int]]] = defaultdict(list)

    for item in items:
        key_tuple = tuple(item.get(k, None) for k in keys)
        grouped[key_tuple].append(item)

    return dict(grouped)

# 测试
students: list[dict[str, str | int]] = [
    {"name": "Alice", "grade": "A", "gender": "F"},
    {"name": "Bob", "grade": "B", "gender": "M"},
    {"name": "Charlie", "grade": "A", "gender": "M"},
    {"name": "Diana", "grade": "B", "gender": "F"},
]

result = group_by_multiple_keys(students, "grade", "gender")
print(result)
# {('A', 'F'): [{'name': 'Alice', ...}],
#  ('B', 'M'): [{'name': 'Bob', ...}],
#  ...}

示例 4:实现 LRU 缓存(使用字典)

class LRUCache:
    """最近最少使用缓存"""

    def __init__(self, capacity: int) -> None:
        self.capacity: int = capacity
        self.cache: dict[int, int] = {}
        self.access_order: list[int] = []

    def get(self, key: int) -> int | None:
        if key not in self.cache:
            return None

        # 更新访问顺序
        self.access_order.remove(key)
        self.access_order.append(key)

        return self.cache[key]

    def put(self, key: int, value: int) -> None:
        if key in self.cache:
            # 更新已有键
            self.cache[key] = value
            self.access_order.remove(key)
            self.access_order.append(key)
        else:
            # 添加新键
            if len(self.cache) >= self.capacity:
                # 删除最久未使用的键
                oldest = self.access_order.pop(0)
                del self.cache[oldest]

            self.cache[key] = value
            self.access_order.append(key)

# 测试
cache = LRUCache(3)
cache.put(1, 1)
cache.put(2, 2)
cache.put(3, 3)
print(cache.get(1))  # 1
cache.put(4, 4)      # 淘汰 key=2
print(cache.get(2))  # None

最佳实践

  1. 优先使用集合进行成员检查:集合的成员检查时间复杂度为 O(1),而列表为 O(n)
# ✗ 不推荐
if item in my_list:
    pass

# ✓ 推荐
if item in my_set:
    pass
  1. 使用 defaultdict 处理缺失键:避免 KeyError
# ✗ 不推荐
d = {}
d["key"] += 1  # KeyError

# ✓ 推荐
from collections import defaultdict
d = defaultdict(int)
d["key"] += 1
  1. 列表推导式优先于 for 循环:更简洁、更 Pythonic
# ✗ 不推荐
result = []
for x in range(10):
    result.append(x ** 2)

# ✓ 推荐
result = [x ** 2 for x in range(10)]
  1. 使用集合进行去重:简单高效
# ✗ 不推荐
unique = []
for x in items:
    if x not in unique:
        unique.append(x)

# ✓ 推荐
unique = list(set(items))  # 注意:可能改变顺序
  1. 理解可变对象的引用传递:避免意外的修改
# 谨慎使用 list 作为默认参数
def func(items=None):
    if items is None:
        items = []  # 创建新列表
    # 不要 items = []

课后练习

练习 6.1:列表操作

给定列表 [5, 2, 8, 1, 9, 3],实现:

  1. 找出最大值和最小值(不使用内置函数)
  2. 计算平均值
  3. 按降序排序

练习 6.2:找出两个列表的交集

编写函数 intersection(list1, list2) 返回两个列表的交集(不重复)。

练习 6.3:合并两个字典

编写函数 merge_dicts(dict1, dict2) 合并两个字典,对于相同的键,取较大的值。

练习 6.4:集合运算

给定两个集合 {1, 2, 3, 4, 5}{4, 5, 6, 7, 8},计算并集、交集、差集和对称差集。

练习 6.5:字典分组

使用 defaultdict 将以下列表按奇偶数分组: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

练习 6.6:列表去重

实现一个函数,去除列表中的重复元素,同时保持原有顺序。

练习 6.7:矩阵转置

使用列表推导式实现矩阵转置:

matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]
# 期望结果:
# [
#   [1, 4, 7],
#   [2, 5, 8],
#   [3, 6, 9]
# ]

练习 6.8:杨辉三角

使用列表推导式生成杨辉三角前 n 行。

练习 6.9:实现计数器

使用 defaultdict 实现一个文本词频统计器。

练习 6.10:实现 LRU 缓存

使用字典和列表实现一个 LRU(最近最少使用)缓存类。

本章小结

本章我们详细学习了 Python 的三种核心数据结构:

  1. 列表 (list)

    • 有序、可变、允许重复元素
    • 支持索引访问和切片
    • 列表推导式是创建列表的简洁方式
  2. 字典 (dict)

    • 键值对映射
    • 高效的查找性能(O(1))
    • defaultdict 自动处理缺失键
  3. 集合 (set)

    • 无序、不重复
    • 适合成员检查和去重
    • 支持数学集合运算

理解这些数据结构的特性和适用场景,能够帮助我们编写更高效、更 Pythonic 的代码。

相关资源


评论