python从入门到精通-第3章: 数据结构 — Python的"瑞士军刀

第3章: 数据结构 --- Python的"瑞士军刀"

Java/Kotlin 开发者习惯了"一个需求一个类"------要排序就 Collections.sort(),要计数就写个 HashMap 循环。Python 的内置数据结构自带大量操作语法,很多你在 JVM 里需要工具类才能做的事,Python 一行搞定。本章逐个拆解四大核心结构 + 切片 + 解包 + collections + 数据类 + 枚举 + 类型注解。


3.1 list vs ArrayList/MutableList

Java/Kotlin 对比

java 复制代码
// Java: ArrayList --- 显式类型,方法名冗长
List<String> names = new ArrayList<>();
names.add("Alice");
names.add(0, "Bob");          // 指定位置插入
names.addAll(List.of("C", "D"));
names.remove(0);               // 按索引删除
names.remove("Alice");         // 按值删除
Collections.reverse(names);
Collections.sort(names);
String first = names.get(0);
int size = names.size();

// 排序需要 Comparator
names.sort(Comparator.comparingInt(String::length));
kotlin 复制代码
// Kotlin: MutableList --- 稍简洁
val names = mutableListOf("Alice", "Bob")
names.add("Charlie")
names.addAll(listOf("D", "E"))
names.removeAt(0)
names.reverse()
names.sortBy { it.length }
val first = names[0]

Python 实现

python 复制代码
# === 创建 ===
names = ["Alice", "Bob", "Charlie"]
empty = []
from_range = list(range(5))        # [0, 1, 2, 3, 4]
from_string = list("hello")        # ['h', 'e', 'l', 'l', 'o']

# === 增删 ===
names.append("Dave")               # 末尾追加 → vs add()
names.insert(0, "Zoe")             # 指定位置插入
names.extend(["Eve", "Frank"])     # 批量追加 → vs addAll()
names += ["Grace"]                 # += 等价于 extend

removed = names.pop()              # 弹出末尾元素
removed = names.pop(0)             # 弹出指定位置
names.remove("Bob")                # 按值删除(只删第一个)
del names[0]                       # 按索引删除

# === 查询 ===
first = names[0]                   # 正索引
last = names[-1]                   # 负索引!Python 独有
idx = names.index("Alice")         # 查找索引
count = names.count("Alice")       # 计数
has = "Alice" in names             # 包含判断 → vs contains()

# === 排序与反转 ===
numbers = [3, 1, 4, 1, 5, 9]
numbers.sort()                     # 原地排序(返回 None)
numbers.sort(reverse=True)         # 降序
numbers.sort(key=abs)              # 按函数排序

sorted_nums = sorted([3, 1, 4])    # 返回新列表,不修改原列表
names_reversed = sorted(names, key=len)  # 按长度排序

numbers.reverse()                  # 原地反转
reversed_nums = list(reversed(numbers))  # 返回新列表

# === 列表推导式(Java/Kotlin 完全没有的语法) ===
squares = [x ** 2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
matrix = [[i * 3 + j for j in range(3)] for i in range(3)]
# [[0,1,2], [3,4,5], [6,7,8]]

# 带条件的推导式
words = ["hello", "world", "", "python"]
non_empty = [w for w in words if w]           # 过滤空串
upper = [w.upper() for w in words if w]       # 过滤 + 转换

# === 其他常用操作 ===
numbers = [1, 2, 3, 4, 5]
print(len(numbers))          # 5
print(sum(numbers))          # 15
print(min(numbers))          # 1
print(max(numbers))          # 5
print(any([0, 0, 1]))        # True --- 任一为真
print(all([1, 2, 3]))        # True --- 全部为真
print("->".join(["a","b"]))  # "a->b" --- 拼接字符串

# === 浅拷贝 vs 深拷贝 ===
a = [[1, 2], [3, 4]]
b = a.copy()          # 浅拷贝:b[0] is a[0] → True!
b[0][0] = 99          # a[0][0] 也变成 99!

import copy
c = copy.deepcopy(a)  # 深拷贝:完全独立

核心差异

特性 Java ArrayList Kotlin MutableList Python list
类型 泛型 List<String> 泛型 MutableList<String> 无类型约束,可混合类型 [1, "a", True]
追加 add(e) add(e) append(e)
批量追加 addAll(list) addAll(list) extend(list)+= list
负索引 不支持 不支持 lst[-1] 取最后一个
排序 Collections.sort() sortBy{} lst.sort() 原地 / sorted(lst) 新建
推导式 [expr for x in iterable if cond]
切片 subList(from, to) subList(from, to) lst[from:to] 语法糖

常见陷阱

python 复制代码
# 陷阱1: sort() 返回 None,不是排序后的列表
nums = [3, 1, 2]
result = nums.sort()       # result 是 None!
print(result)              # None
print(nums)                # [1, 2, 3] --- 原地已排序
# 正确: 用 sorted() 获取新列表
result = sorted(nums)

# 陷阱2: 默认参数的可变陷阱(详见第2章)
def add(item, lst=[]):     # 危险![] 在函数定义时创建一次
    lst.append(item)
    return lst
# 正确:
def add(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

# 陷阱3: 混合类型列表
mixed = [1, "two", [3, 4]]  # 合法但危险!
# sorted(mixed)  # TypeError: '<' not supported between 'int' and 'str'

# 陷阱4: 用 == 比较列表是比较值,is 比较身份
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)   # True  --- 值相等
print(a is b)   # False --- 不是同一个对象

何时使用

  • 用 list: 几乎所有有序、可变集合场景。Python 的 list 就是默认选择,不需要像 Java 那样在 ArrayList/LinkedList 之间纠结。
  • 不用 list 的场景: 需要不可变性时用 tuple(3.2),需要快速查找时用 dict/set(3.3/3.4),需要频繁头插时用 deque(3.7)。

3.2 tuple vs 不可变List/Pair/Triple

Java/Kotlin 对比

java 复制代码
// Java 16+: record(不可变数据载体)
record Point(int x, int y) {}
Point p = new Point(1, 2);
int x = p.x();

// Java: 没有原生 Pair,通常用第三方库
// Map.Entry 或 Apache Commons Lang Pair
Pair<String, Integer> pair = Pair.of("age", 25);

// 返回多值需要包装类或数组
public record Result(String name, int score) {}
kotlin 复制代码
// Kotlin: data class(不可变)
data class Point(val x: Int, val y: Int)
val (x, y) = Point(1, 2)    // 解构声明

// Kotlin 标准库: Pair 和 Triple
val pair = Pair("name", "Alice")
val (key, value) = pair

val triple = Triple(1, "hello", true)
val (a, b, c) = triple

// 不可变 List
val list = listOf(1, 2, 3)   // 不可变

Python 实现

python 复制代码
# === 创建 ===
point = (1, 2)               # 最常用
single = (42,)               # 单元素元组必须有逗号!
empty = ()                   # 空元组
no_parens = 1, 2, 3          # 括号可省略(但推荐保留)
nested = ((1, 2), (3, 4))    # 嵌套元组

# === 索引与切片(和 list 相同) ===
point = (10, 20, 30, 40)
print(point[0])              # 10
print(point[-1])             # 40
print(point[1:3])            # (20, 30) --- 返回元组

# === 解包(Python 核心特性) ===
x, y = (1, 2)                # 基本解包
r, g, b, a = (255, 128, 0, 255)

# 交换变量 --- Python 独有语法
a, b = 1, 2
a, b = b, a                  # 一步完成,无需临时变量
print(a, b)                  # 2 1

# 多返回值 --- Python 函数天然支持
def min_max(numbers):
    return min(numbers), max(numbers)

lo, hi = min_max([3, 1, 4, 1, 5])
print(lo, hi)                # 1 5

# 星号解包
first, *rest = (1, 2, 3, 4, 5)
print(first)                 # 1
print(rest)                  # [2, 3, 4, 5] --- 注意是 list

*head, last = (1, 2, 3, 4, 5)
print(head)                  # [1, 2, 3, 4]
print(last)                  # 5

first, *middle, last = (1, 2, 3, 4, 5)
print(middle)                # [2, 3, 4]

# === 不可变性 ===
point = (1, 2)
# point[0] = 10  # TypeError: 'tuple' object does not support item assignment

# 但如果元组内包含可变对象,该对象本身可变
mutable_in_tuple = ([1, 2], [3, 4])
mutable_in_tuple[0].append(3)    # 合法![1, 2, 3]
# mutable_in_tuple[0] = [9, 9]   # 非法 --- 不能替换整个元素

# === 命名元组 ===
from collections import namedtuple

# 方式1: namedtuple(经典,3.6 仍推荐)
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
print(p.x, p.y)             # 1 2
print(p[0], p[1])           # 1 2 --- 仍支持索引
x, y = p                    # 仍支持解包

# 方式2: typing.NamedTuple(推荐,支持类型注解)
from typing import NamedTuple

class Point(NamedTuple):
    x: int
    y: int
    # 可以有默认值和方法
    label: str = "origin"

    def distance_to(self, other):
        return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5

p = Point(3, 4)
print(p.label)              # "origin"
print(p.distance_to(Point(0, 0)))  # 5.0

# namedtuple 转换
p = Point(1, 2)
print(p._asdict())          # {'x': 1, 'y': 2} --- 转为 dict
print(p._replace(x=10))     # Point(x=10, y=2) --- 创建新实例(不可变替换)

核心差异

特性 Java record Kotlin data class / Pair Python tuple
定义 record Point(int x, int y){} data class Point(val x, val y) point = (1, 2) 无需定义
字段访问 p.x() p.x p[0] 或命名元组 p.x
解构 无原生支持 val (x, y) = p x, y = p
多返回值 需要 record 包装 需要 data class 包装 return a, b 天然支持
不可变保证 编译器强制 val 属性强制 浅不可变(内部可变对象可变)
哈希 自动实现 自动实现 自动实现(可用作 dict key)

常见陷阱

python 复制代码
# 陷阱1: 单元素元组必须加逗号
not_a_tuple = (42)          # int,不是 tuple!
is_a_tuple = (42,)          # tuple,长度为 1
print(type(not_a_tuple))    # <class 'int'>
print(type(is_a_tuple))     # <class 'tuple'>

# 陷阱2: 逗号创建元组,不是括号
a = 1, 2, 3                 # 这是 tuple
b = (1)                     # 这是 int
c = (1,)                    # 这是 tuple

# 陷阱3: 元组"不可变"不等于"深度不可变"
t = ([1, 2], "hello")
t[0].append(3)              # 合法!内部 list 可变
# t[0] = [9, 9]             # 非法 --- 不能替换元素引用

# 陷阱4: 单个值返回不是元组
def return_one():
    return 42               # 返回 int,不是 (42,)
def return_tuple():
    return 42,              # 逗号使其成为 tuple

何时使用

  • 用 tuple: 函数多返回值、数据不需要修改、作为 dict 的 key、数据结构的固定结构(如坐标、RGB)。
  • 用 namedtuple/NamedTuple: 需要字段名访问时(替代简单的 data class,更轻量)。
  • 不用 tuple: 数据需要增删改时用 list。

3.3 dict vs HashMap/Map

Java/Kotlin 对比

java 复制代码
// Java: HashMap
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 30);
ages.put("Bob", 25);
int age = ages.get("Alice");          // 30
int unknown = ages.getOrDefault("Eve", 0);  // 0
ages.putIfAbsent("Alice", 31);        // 不覆盖
ages.replace("Alice", 31);
ages.remove("Bob");

// 遍历
for (Map.Entry<String, Integer> e : ages.entrySet()) {
    System.out.println(e.getKey() + ": " + e.getValue());
}

// Java 8+: merge, computeIfAbsent
ages.merge("Alice", 1, Integer::sum);
ages.computeIfAbsent("Charlie", k -> k.length());
kotlin 复制代码
// Kotlin: mapOf(不可变)/ mutableMapOf(可变)
val ages = mutableMapOf("Alice" to 30, "Bob" to 25)
ages["Charlie"] = 28                   // 下标操作
val age = ages["Alice"]                // 30
val unknown = ages.getOrDefault("Eve", 0)
ages.getOrPut("Dave") { 20 }          // 不存在则计算并放入
ages.remove("Bob")

// 遍历
for ((name, age) in ages) {           // 解构遍历
    println("$name: $age")
}

Python 实现

python 复制代码
# === 创建 ===
d = {"name": "Alice", "age": 30}
empty = {}
from_pairs = dict([("x", 1), ("y", 2)])
from_kwargs = dict(name="Bob", age=25)
keys_only = dict.fromkeys(["a", "b", "c"], 0)  # {'a': 0, 'b': 0, 'c': 0}

# === 增删改查 ===
d = {}
d["name"] = "Alice"             # 添加/修改
d["age"] = 30
name = d["name"]                # 查询(KeyError 如果不存在)
age = d.get("age")              # 安全查询,不存在返回 None
age = d.get("salary", 0)        # 安全查询 + 默认值

# setdefault: 存在则返回,不存在则设置并返回
value = d.setdefault("city", "Beijing")  # 设置 "city" 并返回
value = d.setdefault("name", "Bob")      # "name" 已存在,返回 "Alice"

# 删除
removed = d.pop("age")          # 删除并返回值
d.pop("nonexistent", None)      # 不存在时返回默认值,不报错
del d["name"]                   # 删除(不存在则 KeyError)
removed = d.popitem()           # 删除并返回最后一个 (key, value)

# === 合并字典 ===
defaults = {"theme": "dark", "lang": "en"}
user_config = {"lang": "zh", "font": "14px"}

# 方式1: | 运算符 [3.9+] --- 最简洁
config = defaults | user_config
# {'theme': 'dark', 'lang': 'zh', 'font': '14px'}

# 方式2: |= 原地合并 [3.9+]
config = defaults.copy()
config |= user_config

# 方式3: {**a, **b} --- 3.5+ 通用
config = {**defaults, **user_config}

# 方式4: update() --- 原地修改
config = defaults.copy()
config.update(user_config)

# === 遍历 ===
scores = {"Alice": 90, "Bob": 85, "Charlie": 92}

for key in scores:                      # 只遍历 key
    print(key)

for key in scores.keys():               # 显式遍历 key
    print(key)

for value in scores.values():           # 遍历 value
    print(value)

for key, value in scores.items():       # 遍历 key-value(最常用)
    print(f"{key}: {value}")

# === dict 推导式 ===
squares = {x: x ** 2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# 翻转 key-value
original = {"a": 1, "b": 2, "c": 3}
flipped = {v: k for k, v in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}

# 过滤
scores = {"Alice": 90, "Bob": 55, "Charlie": 72}
passed = {k: v for k, v in scores.items() if v >= 60}
# {'Alice': 90, 'Charlie': 72}

# === 嵌套访问 ===
data = {"user": {"profile": {"name": "Alice"}}}

# 直接访问(不安全)
name = data["user"]["profile"]["name"]

# 安全访问(Python 没有?. 语法,需要手动处理)
def get_nested(d, *keys, default=None):
    """安全获取嵌套字典的值"""
    current = d
    for key in keys:
        if isinstance(current, dict):
            current = current.get(key)
            if current is None:
                return default
        else:
            return default
    return current

name = get_nested(data, "user", "profile", "name")
missing = get_nested(data, "user", "settings", "theme", default="dark")

# === 有序性 ===
# Python 3.7+: dict 保证插入顺序(不是按 key 排序)
d = {"c": 3, "a": 1, "b": 2}
print(list(d.keys()))  # ['c', 'a', 'b'] --- 插入顺序

核心差异

特性 Java HashMap Kotlin mapOf Python dict
有序性 LinkedHashMap 才有序 LinkedHashMap 才有序 3.7+ 天然有序
安全取值 getOrDefault() getOrDefault() d.get(key, default)
不存在时计算 computeIfAbsent() getOrPut() setdefault()defaultdict
合并 putAll() + 运算符 `
推导式 {k: v for k, v in ...}
遍历 entrySet() for ((k,v) in map) for k, v in d.items()

常见陷阱

python 复制代码
# 陷阱1: d[key] 不存在会抛 KeyError,不是返回 null
d = {"a": 1}
# d["b"]           # KeyError: 'b'
d.get("b")         # None --- 安全
d.get("b", 0)      # 0 --- 安全 + 默认值

# 陷阱2: 遍历时不能修改字典大小
d = {"a": 1, "b": 2, "c": 3}
# for k in d:
#     if k == "b":
#         del d[k]    # RuntimeError: dictionary changed size during iteration
# 正确:
for k in list(d.keys()):   # 先复制 key 列表
    if k == "b":
        del d[k]

# 陷阱3: 可变对象作为 key
d = {}
key = [1, 2]
# d[key] = "value"   # TypeError: unhashable type: 'list'
d[tuple(key)] = "value"    # 用 tuple 代替

# 陷阱4: setdefault 的"计算"参数总是会被求值
# d.setdefault("key", expensive_function())  # expensive_function 总是会执行
# 如果需要惰性求值,用:
if "key" not in d:
    d["key"] = expensive_function()

何时使用

  • 用 dict: 键值映射、配置、JSON 数据、计数器(或用 Counter)、缓存。
  • 用 defaultdict: 当缺失 key 需要自动初始化时(3.7 详述)。
  • 不用 dict : 需要排序的键值对时考虑 OrderedDict;需要不可变时考虑 types.MappingProxyType

3.4 set vs HashSet/Set

Java/Kotlin 对比

java 复制代码
// Java: HashSet
Set<String> names = new HashSet<>();
names.add("Alice");
names.add("Bob");
names.add("Alice");           // 重复,不报错但不会添加
boolean has = names.contains("Alice");
names.remove("Bob");

// 集合运算需要 Stream 或 Guava
Set<String> a = Set.of("a", "b", "c");
Set<String> b = Set.of("b", "c", "d");

// 并集
Set<String> union = new HashSet<>(a);
union.addAll(b);

// 交集
Set<String> intersection = new HashSet<>(a);
intersection.retainAll(b);

// 差集
Set<String> diff = new HashSet<>(a);
diff.removeAll(b);
kotlin 复制代码
// Kotlin: setOf(不可变)/ mutableSetOf(可变)
val setA = setOf("a", "b", "c")
val setB = setOf("b", "c", "d")

val union = setA union setB          // {"a", "b", "c", "d"}
val intersect = setA intersect setB   // {"b", "c"}
val diff = setA - setB               // {"a"}
val symmetric = setA xor setB        // {"a", "d"}

Python 实现

python 复制代码
# === 创建 ===
s = {1, 2, 3}
empty = set()                    # 注意: {} 是空 dict,不是空 set!
from_list = set([1, 2, 2, 3])   # {1, 2, 3} --- 自动去重
from_string = set("hello")       # {'h', 'e', 'l', 'o'} --- 去重

# === 基本操作 ===
s = {1, 2, 3}
s.add(4)                        # 添加元素
s.discard(4)                    # 删除(不存在不报错)
s.remove(3)                     # 删除(不存在抛 KeyError)
s.pop()                         # 随机弹出一个元素
s.clear()                       # 清空

# === 集合运算(Python 用运算符,最直观) ===
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

print(a | b)                    # {1, 2, 3, 4, 5, 6} --- 并集
print(a & b)                    # {3, 4}              --- 交集
print(a - b)                    # {1, 2}              --- 差集(a 有 b 没有)
print(a ^ b)                    # {1, 2, 5, 6}        --- 对称差集
print(a <= b)                   # False --- a 是否是 b 的子集
print(a >= b)                   # False --- a 是否是 b 的超集
print({3, 4} <= a)              # True  --- 子集判断
print(a < a)                    # False --- 真子集
print(a == {1, 2, 3, 4})       # True  --- 相等判断

# 方法形式(等价于运算符)
print(a.union(b))               # 并集
print(a.intersection(b))        # 交集
print(a.difference(b))          # 差集
print(a.symmetric_difference(b))# 对称差集
print(a.issubset(b))            # 子集
print(a.issuperset(b))          # 超集

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

evens = {x for x in range(20) if x % 2 == 0}
# {0, 2, 4, 6, 8, 10, 12, 14, 16, 18}

# === frozenset: 不可变集合 ===
fs = frozenset([1, 2, 3])
# fs.add(4)    # AttributeError: 'frozenset' object has no attribute 'add'
print(fs | {4})               # frozenset({1, 2, 3, 4}) --- 运算返回 frozenset

# frozenset 可作为 dict 的 key 或 set 的元素
d = {frozenset([1, 2]): "pair"}
nested_set = {frozenset([1, 2]), frozenset([2, 3])}

# === 实用场景 ===
# 1. 去重
names = ["Alice", "Bob", "Alice", "Charlie", "Bob"]
unique = list(set(names))           # ['Alice', 'Bob', 'Charlie'](顺序不保证)

# 2. 成员检测(set 比 list 快得多)
big_set = set(range(1_000_000))
print(999999 in big_set)            # O(1),极快
# vs list: O(n),慢

# 3. 消除交集
common = {1, 2, 3} & {2, 3, 4}     # {2, 3}

核心差异

特性 Java HashSet Kotlin setOf Python set
空集合 new HashSet<>() emptySet() set() --- 注意不是 {}
并集 addAll() union 中缀函数 `
交集 retainAll() intersect 中缀函数 & 运算符
差集 removeAll() - 运算符 - 运算符
对称差 无原生支持 xor 中缀函数 ^ 运算符
不可变 Set.of() [Java 9+] setOf() frozenset()
推导式 {x for x in ...}

常见陷阱

python 复制代码
# 陷阱1: {} 是空 dict,不是空 set
empty_set = set()          # 正确
# empty_set = {}           # 这是空 dict!
print(type({}))            # <class 'dict'>

# 陷阱2: set 元素必须是可哈希的
# s = {[1, 2]}             # TypeError: unhashable type: 'list'
s = {(1, 2)}               # tuple 可以
s = {frozenset([1, 2])}    # frozenset 也可以

# 陷阱3: set 无序(虽然 CPython 3.7+ 实现上有序,但不保证)
# 不要依赖 set 的遍历顺序,需要顺序时转 list 并排序

# 陷阱4: set 不能索引
s = {1, 2, 3}
# s[0]                     # TypeError: 'set' object is not subscriptable

何时使用

  • 用 set : 去重、成员检测(in 操作频繁时)、集合运算(交并差)。
  • 用 frozenset: 需要不可变集合、作为 dict key 或 set 元素时。
  • 不用 set: 需要保持顺序时用 list;需要键值映射时用 dict。

3.5 切片 [start:stop:step]

Python 独有核心概念。Java 的 subList()、Kotlin 的 slice() 只是方法调用,Python 把切片做成了语言级语法,无处不在。

Java/Kotlin 对比

java 复制代码
// Java: subList 返回视图(不是副本!)
List<String> list = List.of("a", "b", "c", "d", "e");
List<String> sub = list.subList(1, 3);  // ["b", "c"] --- 不含索引3
// 注意: subList 是原列表的视图,修改 sub 会影响原列表

// 没有步长、负索引、反转切片等概念
kotlin 复制代码
// Kotlin: slice 返回新列表
val list = listOf("a", "b", "c", "d", "e")
val sub = list.slice(1..2)       // ["b", "c"] --- 含两端
val drop = list.drop(2)          // ["c", "d", "e"]
val take = list.take(3)          // ["a", "b", "c"]
val reversed = list.reversed()   // ["e", "d", "c", "b", "a"]

Python 实现

python 复制代码
# === 基本切片 ===
s = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# [start:stop] --- 含 start,不含 stop(和 Java/Kotlin 一致)
print(s[2:5])        # [2, 3, 4]
print(s[:5])         # [0, 1, 2, 3, 4] --- 从头开始
print(s[5:])         # [5, 6, 7, 8, 9] --- 到末尾
print(s[:])          # [0, 1, 2, ..., 9] --- 完整拷贝
print(s[-3:])        # [7, 8, 9] --- 最后3个
print(s[:-3])        # [0, 1, 2, 3, 4, 5, 6] --- 除最后3个

# === 步长 step ===
# [start:stop:step]
print(s[::2])        # [0, 2, 4, 6, 8] --- 每隔一个取
print(s[1::2])       # [1, 3, 5, 7, 9] --- 从索引1开始,每隔一个
print(s[::3])        # [0, 3, 6, 9] --- 每隔两个取
print(s[::-1])       # [9, 8, 7, ..., 0] --- 反转!
print(s[::-2])       # [9, 7, 5, 3, 1] --- 反转 + 步长
print(s[8:2:-2])     # [8, 6, 4] --- 反向切片

# === 负索引详解 ===
s = ['a', 'b', 'c', 'd', 'e']
# 正索引:  0    1    2    3    4
# 负索引: -5   -4   -3   -2   -1
print(s[-1])         # 'e' --- 最后一个
print(s[-2])         # 'd' --- 倒数第二个
print(s[-3:])        # ['c', 'd', 'e'] --- 最后三个
print(s[:-1])        # ['a', 'b', 'c', 'd'] --- 除最后一个
print(s[1:-1])       # ['b', 'c', 'd'] --- 去头去尾

# === 切片不越界 ===
s = [0, 1, 2, 3]
print(s[1:100])      # [1, 2, 3] --- 自动截断,不报错
print(s[-100:2])     # [0, 1] --- 自动从0开始
print(s[10:20])      # [] --- 空列表,不报错
# 对比: s[10] 会抛 IndexError,但 s[10:20] 不会

# === 切片赋值 ===
s = [0, 1, 2, 3, 4, 5]

# 替换一段
s[1:4] = [10, 20]    # [0, 10, 20, 4, 5] --- 长度可以不同
s[1:3] = []          # [0, 4, 5] --- 删除一段

# 插入
s = [0, 1, 2]
s[1:1] = [10, 20]    # [0, 10, 20, 1, 2] --- 在位置1插入

# 用步长切片赋值(元素数量必须匹配)
s = [0, 1, 2, 3, 4, 5]
s[::2] = [10, 20, 30]  # [10, 1, 20, 3, 30, 5]

# === slice 对象(高级用法) ===
# slice() 创建可复用的切片对象
first_three = slice(3)
every_other = slice(None, None, 2)
last_two = slice(-2, None)

s = [0, 1, 2, 3, 4, 5, 6, 7]
print(s[first_three])   # [0, 1, 2]
print(s[every_other])   # [0, 2, 4, 6]
print(s[last_two])      # [6, 7]

# 在自定义类中支持切片
class MyList:
    def __getitem__(self, key):
        if isinstance(key, slice):
            return f"slice({key.start}, {key.stop}, {key.step})"
        return f"index({key})"

ml = MyList()
print(ml[3])            # index(3)
print(ml[1:5:2])        # slice(1, 5, 2)

# === 切片适用于所有序列类型 ===
text = "Hello, World!"
print(text[7:12])       # "World"
print(text[::-1])       # "!dlroW ,olleH"

t = (0, 1, 2, 3, 4)
print(t[1:4])           # (1, 2, 3)

核心差异

特性 Java subList Kotlin slice Python 切片
语法 list.subList(1, 3) list.slice(1..2) list[1:3]
返回类型 视图(修改影响原列表) 新列表 新列表(浅拷贝)
负索引 不支持 不支持 list[-1] 取最后
步长 不支持 不支持 list[::2]
反转 Collections.reverse() reversed() list[::-1]
越界 抛异常 抛异常 自动截断,不报错

常见陷阱

python 复制代码
# 陷阱1: 切片是浅拷贝
a = [[1, 2], [3, 4]]
b = a[:]
b[0][0] = 99           # a[0][0] 也变成 99!
# 需要 copy.deepcopy() 才能完全独立

# 陷阱2: 步长切片赋值元素数量必须匹配
s = [0, 1, 2, 3, 4]
# s[::2] = [10, 20]    # ValueError: attempt to assign sequence of size 2
                        # to extended slice of size 3
s[::2] = [10, 20, 30]  # 正确

# 陷阱3: 切片 vs 索引的越界行为不同
s = [0, 1, 2]
# s[5]                  # IndexError
print(s[5:10])          # [] --- 切片不越界

# 陷阱4: 逆序步长的 start/stop 逻辑
s = [0, 1, 2, 3, 4, 5]
print(s[5:1:-1])        # [5, 4, 3, 2] --- 注意: 不含 stop
print(s[5:1:-2])        # [5, 3] --- 步长也影响方向

何时使用

  • 切片无处不在: 取子序列、反转、分页、跳步------只要操作序列就优先考虑切片。
  • slice 对象: 当同一切片模式需要在多处复用时。

3.6 解包与星号表达式

Python 的解包(unpacking)远比 Java/Kotlin 的解构声明强大。它是语言级特性,不仅用于赋值,还用于函数调用、集合构造等场景。

Java/Kotlin 对比

java 复制代码
// Java: 没有原生解构
// 只能手动赋值
int[] arr = {1, 2, 3};
int a = arr[0], b = arr[1], c = arr[2];

// Java 21+: record 模式匹配(有限)
record Point(int x, int y) {}
if (obj instanceof Point(int x, int y)) {
    System.out.println(x + ", " + y);
}
kotlin 复制代码
// Kotlin: 解构声明
val (name, age) = Pair("Alice", 30)
val (x, y, z) = Triple(1, 2, 3)

data class Person(val name: String, val age: Int)
val (name, age) = Person("Bob", 25)

// 只取部分(用 _ 忽略)
val (_, age) = Pair("Alice", 30)

// 在循环中解构
val map = mapOf("a" to 1, "b" to 2)
for ((key, value) in map) { }

Python 实现

python 复制代码
# === 基本解包 ===
a, b, c = [1, 2, 3]
a, b, c = (1, 2, 3)
a, b, c = "abc"              # 字符串也是序列!
x, y, z = range(3)           # range 也是序列

# 交换变量(Pythonic)
a, b = 1, 2
a, b = b, a                  # 一步完成,无需临时变量

# === 忽略值 ===
a, _, c = [1, 2, 3]          # _ 约定表示忽略(但 _ 仍是一个变量)
a, *_, c = [1, 2, 3, 4, 5]  # 忽略中间所有

# === 星号解包(收集剩余元素) ===
first, *rest = [1, 2, 3, 4, 5]
print(first)                 # 1
print(rest)                  # [2, 3, 4, 5] --- list 类型

*head, last = [1, 2, 3, 4, 5]
print(head)                  # [1, 2, 3, 4]
print(last)                  # 5

first, *middle, last = [1, 2, 3, 4, 5]
print(middle)                # [2, 3, 4]

# 星号解包在 for 循环中
for first, *rest in [[1, 2, 3], [4, 5, 6, 7]]:
    print(first, rest)
# 1 [2, 3]
# 4 [5, 6, 7]

# === 嵌套解包 ===
(a, b), (c, d) = (1, 2), (3, 4)
print(a, b, c, d)            # 1 2 3 4

matrix = [[1, 2], [3, 4], [5, 6]]
for row_idx, (x, y) in enumerate(matrix):
    print(row_idx, x, y)

# 嵌套 + 星号
first, (second, *rest), last = [1, (2, 3, 4), 5]
print(first, second, rest, last)  # 1 2 [3, 4] 5

# === 星号在函数调用中(展开参数) ===
def add(a, b, c):
    return a + b + c

nums = [1, 2, 3]
print(add(*nums))            # 6 --- 展开列表为位置参数
print(add(*range(3)))        # 3 --- 展开任意可迭代对象

# === 双星号在函数调用中(展开关键字参数) ===
def greet(name, age):
    return f"{name}, {age}"

info = {"name": "Alice", "age": 30}
print(greet(**info))         # "Alice, 30" --- 展开 dict 为关键字参数

# === 星号在集合构造中 ===
# 合并列表
a = [1, 2, 3]
b = [4, 5]
c = [*a, *b, 6]             # [1, 2, 3, 4, 5, 6]

# 合并集合
s1 = {1, 2}
s2 = {3, 4}
s3 = {*s1, *s2}             # {1, 2, 3, 4}

# 合并字典 [3.5+]
d1 = {"a": 1}
d2 = {"b": 2}
d3 = {**d1, **d2, "c": 3}   # {'a': 1, 'b': 2, 'c': 3}

# === 双星号在函数定义中(收集关键字参数) ===
def config(**kwargs):
    for key, value in kwargs.items():
        print(f"  {key} = {value}")

config(host="localhost", port=8080, debug=True)
#   host = localhost
#   port = 8080
#   debug = True

# 混合使用
def func(a, b, *args, key="default", **kwargs):
    print(f"a={a}, b={b}, args={args}, key={key}, kwargs={kwargs}")

func(1, 2, 3, 4, key="custom", extra="data")
# a=1, b=2, args=(3, 4), key=custom, kwargs={'extra': 'data'}

# === 实用模式 ===
# 1. 分割头部和尾部
first, *middle, second_last, last = range(10)
print(first, second_last, last)  # 0 8 9

# 2. 展开嵌套列表
nested = [[1, 2], [3, 4], [5, 6]]
flat = [*inner for inner in nested]   # [1, 2, 3, 4, 5, 6]
# 更好的方式:
flat = [item for inner in nested for item in inner]

# 3. 函数参数转发
def wrapper(*args, **kwargs):
    print("before")
    result = target(*args, **kwargs)  # 转发所有参数
    print("after")
    return result

核心差异

特性 Kotlin 解构 Java 21+ 模式匹配 Python 解包
语法 val (a, b) = pair if (obj instanceof R(int a, int b)) a, b = pair
收集剩余 不支持 不支持 first, *rest = seq
忽略值 val (_, b) = pair _ 占位符 a, _, c = seq
交换变量 不支持 不支持 a, b = b, a
函数参数展开 spread operator * 不支持 *args, **kwargs
集合展开 不支持 不支持 [*a, *b], {**d1, **d2}
嵌套解包 有限支持 有限支持 完全支持

常见陷阱

python 复制代码
# 陷阱1: *rest 总是返回 list
first, *rest = (1, 2, 3)
print(type(rest))            # <class 'list'> --- 即使源是 tuple

# 陷阱2: 不能同时有两个 * 解包
# a, *b, *c = [1, 2, 3, 4]  # SyntaxError: two starred expressions

# 陷阱3: 解包数量必须匹配
# a, b = [1, 2, 3]           # ValueError: too many values to unpack
a, b, *_ = [1, 2, 3]        # 正确 --- 用 * 吸收多余的

# 陷阱4: 单星号在函数定义中是元组,不是列表
def func(*args):
    print(type(args))        # <class 'tuple'>

# 陷阱5: _ 在 REPL 中会保留最后一个值
# 在脚本中 _ 只是普通变量名,但在交互式环境中 _ 保存上次结果

何时使用

  • 基本解包: 函数多返回值、交换变量、遍历时解构。
  • 星号解包: 处理不定长序列、分割头尾。
  • *args/**kwargs: 装饰器、参数转发、灵活 API。
  • 集合展开: 合并多个集合/字典。

3.7 collections 模块

Java 需要引入 Guava 或 Apache Commons 才能获得的高级集合工具,Python 标准库直接提供。

Java/Kotlin 对比

java 复制代码
// Java + Guava:
// Multimap<String, Integer> multi = ArrayListMultimap.create();
// multi.put("key", 1);
// multi.put("key", 2);

// Java + Guava: MultiSet
// Multiset<String> counts = HashMultiset.create();
// counts.add("hello");
// counts.count("hello");  // 1

// Java: LinkedHashMap(保持插入顺序)
Map<String, Integer> ordered = new LinkedHashMap<>();

// Java: ArrayDeque
Deque<String> deque = new ArrayDeque<>();
deque.addFirst("a");
deque.addLast("b");
kotlin 复制代码
// Kotlin + kotlinx.collections.immutable:
// val persistentList = persistentListOf(1, 2, 3)

// Kotlin: LinkedHashMap
val ordered = linkedMapOf("a" to 1, "b" to 2)

// Kotlin: ArrayDeque (1.3.70+)
val deque = ArrayDeque(listOf("a", "b"))
deque.addFirst("c")
deque.addLast("d")

Python 实现

python 复制代码
from collections import defaultdict, Counter, OrderedDict, deque, ChainMap, namedtuple

# === defaultdict: 自动初始化缺失 key ===
# Java: computeIfAbsent() / Kotlin: getOrPut()

# 场景1: 分组
words = ["apple", "banana", "avocado", "blueberry", "cherry"]
by_letter = defaultdict(list)
for word in words:
    by_letter[word[0]].append(word)
print(dict(by_letter))
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}

# 场景2: 计数
counts = defaultdict(int)
for word in words:
    counts[len(word)] += 1
print(dict(counts))
# {5: 1, 6: 2, 7: 1, 9: 1}

# 场景3: 嵌套字典
nested = defaultdict(lambda: defaultdict(int))
nested["user"]["clicks"] += 1
nested["user"]["clicks"] += 1
nested["admin"]["clicks"] += 1
print(dict(nested))
# {'user': defaultdict(<class 'int'>, {'clicks': 2}),
#  'admin': defaultdict(<class 'int'>, {'clicks': 1})}

# === Counter: 计数器 ===
# Java: Guava Multiset / Kotlin: 手动 HashMap

text = "the quick brown fox jumps over the lazy dog"
word_counts = Counter(text.split())
print(word_counts.most_common(3))
# [('the', 2), ('quick', 1), ('brown', 1)]

# 基本操作
c = Counter("abracadabra")
print(c)                    # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
print(c['a'])               # 5
print(c['z'])               # 0 --- 不存在的 key 返回 0,不报错
c['a'] -= 1                 # 减少计数
c.update("xyz")             # 增加计数

# 集合运算
c1 = Counter([1, 2, 2, 3])
c2 = Counter([2, 2, 4])
print(c1 + c2)              # Counter({2: 4, 1: 1, 3: 1, 4: 1}) --- 合并
print(c1 - c2)              # Counter({1: 1, 3: 1}) --- 差集(不出现负数)
print(c1 & c2)              # Counter({2: 2}) --- 交集取最小
print(c1 | c2)              # Counter({1: 1, 2: 2, 3: 1, 4: 1}) --- 并集取最大

# top-N
top3 = word_counts.most_common(3)

# === OrderedDict: 有序字典 ===
# Python 3.7+ 普通 dict 已保证插入顺序
# OrderedDict 的额外价值: move_to_end, popitem(last=False)

od = OrderedDict([("a", 1), ("b", 2), ("c", 3)])

# 移动到末尾(LRU 缓存核心操作)
od.move_to_end("a")         # ['b', 'c', 'a']
od.move_to_end("a", last=False)  # ['a', 'b', 'c'] --- 移动到开头

# FIFO 弹出
od.popitem(last=False)      # 弹出第一个 ('a', 1)
od.popitem(last=True)       # 弹出最后一个 ('c', 3)

# === deque: 双端队列 ===
# Java: ArrayDeque / Kotlin: ArrayDeque
# list 的 append/pop 是 O(1),但 insert(0, x) 是 O(n)
# deque 的两端操作都是 O(1)

d = deque([1, 2, 3])

# 右端操作
d.append(4)                 # deque([1, 2, 3, 4])
d.pop()                     # deque([1, 2, 3])

# 左端操作
d.appendleft(0)             # deque([0, 1, 2, 3])
d.popleft()                 # deque([1, 2, 3])

# 旋转
d = deque([1, 2, 3, 4, 5])
d.rotate(2)                 # deque([4, 5, 1, 2, 3]) --- 右移2位
d.rotate(-1)                # deque([5, 1, 2, 3, 4]) --- 左移1位

# 固定长度(自动丢弃溢出)
recent = deque(maxlen=3)
for i in range(5):
    recent.append(i)
print(recent)               # deque([2, 3, 4]) --- 只保留最后3个

# === ChainMap: 链式字典(多层查找) ===
# 类似 CSS 变量覆盖: 先查当前层,没有则查父层

defaults = {"theme": "dark", "lang": "en", "font": "14px"}
user_config = {"lang": "zh"}
env_config = {"font": "16px"}

# 按优先级链接: env_config > user_config > defaults
config = ChainMap(env_config, user_config, defaults)

print(config["lang"])       # "zh" --- user_config 优先
print(config["theme"])      # "dark" --- defaults 兜底
print(config["font"])       # "16px" --- env_config 优先

# 修改只影响第一层
config["theme"] = "light"   # 修改 env_config
print(env_config)           # {'font': '16px', 'theme': 'light'}

# 新增也只影响第一层
config["debug"] = True      # 添加到 env_config

# === namedtuple(详见 3.2,此处补充高级用法) ===
Point = namedtuple('Point', ['x', 'y'], defaults=[0, 0])
p = Point()                 # Point(x=0, y=0) --- 全默认值
p = Point(1)                # Point(x=1, y=0) --- 部分默认值

# 转换
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
print(p._asdict())          # {'x': 1, 'y': 2}
print(p._fields)            # ('x', 'y')
print(p._make([3, 4]))      # Point(x=3, y=4) --- 从 iterable 创建

核心差异

工具 Java 对应 Kotlin 对应 Python collections
defaultdict computeIfAbsent() getOrPut() defaultdict
Counter Guava Multiset Counter
OrderedDict LinkedHashMap linkedMapOf() OrderedDict(3.7+ 普通 dict 也有序)
deque ArrayDeque ArrayDeque deque
ChainMap 无直接对应 无直接对应 ChainMap
namedtuple record data class namedtuple / NamedTuple

常见陷阱

python 复制代码
# 陷阱1: defaultdict 的默认值工厂是无参函数
# defaultdict(list) --- 正确,list() 返回 []
# defaultdict(list()) --- 错误!list() 已经执行了,传入的是 []
# defaultdict(int) --- 正确,int() 返回 0
# defaultdict(0) --- 错误!0 不是可调用对象

# 陷阱2: Counter 不存在的 key 返回 0
c = Counter(["a", "b", "a"])
print(c["z"])               # 0 --- 不报错
# 但遍历时不会出现计数为 0 的 key
print(list(c.keys()))        # ['a', 'b']

# 陷阱3: deque 不支持切片
d = deque([1, 2, 3, 4])
# d[1:3]                   # TypeError: sequence index must be integer
# 需要切片时转 list: list(d)[1:3]

# 陷阱4: ChainMap 查找是动态的
defaults = {"x": 1}
user = {}
chain = ChainMap(user, defaults)
print(chain["x"])           # 1
defaults["x"] = 2           # 修改 defaults
print(chain["x"])           # 2 --- 动态反映变化!

何时使用

  • defaultdict: 分组、嵌套字典、计数------任何需要"key 不存在时自动初始化"的场景。
  • Counter: 词频统计、投票计数、top-N。
  • OrderedDict : LRU 缓存(配合 move_to_end)、需要 popitem(last=False) 的场景。
  • deque: 队列、栈、滑动窗口、最近 N 条记录。
  • ChainMap: 配置层级覆盖(默认配置 < 用户配置 < 环境变量 < 命令行参数)。

3.8 数据类: dataclasses, NamedTuple, TypedDict

Java/Kotlin 对比

java 复制代码
// Java 16+: record
public record Point(int x, int y) {
    // 自动生成: constructor, getX(), getY(), equals(), hashCode(), toString()
}

// Java: 传统 POJO 需要大量样板代码
public class Person {
    private String name;
    private int age;
    // constructor, getters, setters, equals, hashCode, toString...
}

// Java: Lombok 减少样板
@Data
public class Person {
    private String name;
    private int age;
}
kotlin 复制代码
// Kotlin: data class(一等公民)
data class Person(val name: String, val age: Int = 0)
// 自动生成: componentN(), copy(), equals(), hashCode(), toString()

val p1 = Person("Alice", 30)
val p2 = p1.copy(age = 31)         // 不可变修改
val (name, age) = p1                // 解构

Python 实现

python 复制代码
from dataclasses import dataclass, field, FrozenInstanceError
from typing import NamedTuple, TypedDict
import json

# === @dataclass: Python 的 data class ===

@dataclass
class Point:
    x: float
    y: float

p = Point(1.0, 2.0)
print(p)                    # Point(x=1.0, y=2.0) --- 自动 __repr__
print(p.x, p.y)            # 1.0 2.0
print(p == Point(1.0, 2.0))  # True --- 自动 __eq__

# 默认值
@dataclass
class Person:
    name: str
    age: int = 0
    email: str = ""

p = Person("Alice")         # Person(name='Alice', age=0, email='')
p = Person("Bob", age=25, email="bob@test.com")

# frozen=True: 不可变(类似 Kotlin val / Java record)
@dataclass(frozen=True)
class ImmutablePoint:
    x: float
    y: float

p = ImmutablePoint(1.0, 2.0)
# p.x = 3.0                # FrozenInstanceError: cannot assign to field 'x'

# frozen 的好处: 可哈希,可用作 dict key / set 元素
points = {ImmutablePoint(1, 2), ImmutablePoint(1, 2)}
print(len(points))          # 1 --- 自动去重

# field() 高级配置
@dataclass
class Team:
    name: str
    members: list[str] = field(default_factory=list)  # 可变默认值必须用 factory
    scores: list[int] = field(default_factory=list)
    id: int = field(default=0, init=False)            # 不参与 __init__
    _internal: dict = field(default_factory=dict, repr=False, compare=False)

t = Team("Alpha")
t.members.append("Alice")   # 每个实例独立 list
t2 = Team("Beta")
print(t.members)            # ['Alice'] --- 不影响 t2

# === dataclass vs Kotlin data class 对比 ===

# replace() --- 类似 Kotlin 的 copy()
@dataclass
class Config:
    host: str = "localhost"
    port: int = 8080
    debug: bool = False

c = Config()
c2 = c.replace(port=9090, debug=True)  # 类似 Kotlin: c.copy(port=9090, debug=true)
print(c)                   # Config(host='localhost', port=8080, debug=False) --- 原对象不变
print(c2)                  # Config(host='localhost', port=9090, debug=True)

# === typing.NamedTuple: 轻量不可变数据类 ===

class Point(NamedTuple):
    x: float
    y: float
    label: str = "origin"   # 支持默认值

    def distance_to(self, other):
        return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5

p = Point(3, 4)
print(p.label)              # "origin"
print(p.distance_to(Point(0, 0)))  # 5.0
print(p._asdict())          # {'x': 3.0, 'y': 4.0, 'label': 'origin'}

# NamedTuple vs dataclass(frozen=True):
# - NamedTuple: 更轻量,支持元组解包,可哈希
# - dataclass(frozen=True): 更灵活,支持 field()、post_init、继承

# === TypedDict [3.8+]: 带类型提示的字典 ===
# 用于标注 dict 的 key 和 value 类型(运行时不检查)

class PersonDict(TypedDict):
    name: str
    age: int
    email: str

# 类型检查器(mypy/pyright)会检查类型
def greet(person: PersonDict) -> str:
    return f"Hello, {person['name']}"

p: PersonDict = {"name": "Alice", "age": 30, "email": "a@b.com"}
print(greet(p))

# 运行时不检查 --- TypedDict 只是类型提示
p2: PersonDict = {"name": "Bob", "age": "not a number", "email": "b@c.com"}
# mypy 会报错,但 Python 运行时不报错

# 总是可选的字段
class MovieDict(TypedDict, total=False):
    title: str
    year: int
    rating: float

m: MovieDict = {"title": "Inception"}  # 只有 title,其他可选

# === 实战: 选择哪个? ===

# 1. 简单数据载体,不需要方法 → NamedTuple
# 2. 需要默认值、方法、field 控制 → @dataclass
# 3. 需要不可变 → @dataclass(frozen=True) 或 NamedTuple
# 4. 需要灵活的 key-value(如 JSON)→ TypedDict 或普通 dict
# 5. 需要继承和多态 → @dataclass(NamedTuple 不支持继承自定义方法以外的类)

核心差异

特性 Java record Kotlin data class Python @dataclass Python NamedTuple
定义 record P(int x, int y){} data class P(val x, val y) @dataclass class P: class P(NamedTuple):
不可变 天然不可变 val 属性 frozen=True 天然不可变
可变默认值 N/A N/A field(default_factory=...) 不支持可变默认值
不可变拷贝 无(构造新实例) copy(age=31) replace(age=31) _replace(age=31)
解构 val (x,y) = p 不支持(需手动) x, y = p
继承 可实现接口 可继承非 data class 可继承 有限
类型检查 编译期 编译期 mypy(可选) mypy(可选)

常见陷阱

python 复制代码
# 陷阱1: dataclass 的可变默认值
from dataclasses import dataclass, field

@dataclass
class Bad:
    items: list = []        # 危险!所有实例共享同一个 list

@dataclass
class Good:
    items: list = field(default_factory=list)  # 正确

# 陷阱2: dataclass 默认值必须在非默认值之后
# @dataclass
# class Bad:
#     name: str = ""        # 有默认值
#     age: int              # 无默认值 --- TypeError!

@dataclass
class Good:
    age: int                # 无默认值在前
    name: str = ""          # 有默认值在后

# 陷阱3: frozen dataclass 的 __post_init__ 不能修改字段
@dataclass(frozen=True)
class Bad:
    x: int
    y: int
    def __post_init__(self):
        # self.x = 0        # FrozenInstanceError
        pass

# 陷阱4: TypedDict 运行时不检查
# TypedDict 只帮助类型检查器,Python 运行时完全忽略

何时使用

  • @dataclass: 通用数据类,大部分场景的首选。需要可变性、默认值、方法时使用。
  • NamedTuple: 轻量不可变数据、需要元组解包、作为函数返回值。
  • TypedDict: 处理 JSON/字典数据时提供类型提示,配合 mypy 使用。

3.9 enum.Enum

Java/Kotlin 对比

java 复制代码
// Java: enum(功能丰富)
public enum Direction {
    NORTH, SOUTH, EAST, WEST;

    public double toRadians() {
        return switch (this) {
            case NORTH -> 0.0;
            case EAST -> Math.PI / 2;
            // ...
        };
    }
}

// Java: 枚举可以带字段和方法
public enum Status {
    OK(200), NOT_FOUND(404), ERROR(500);
    private final int code;
    Status(int code) { this.code = code; }
    public int getCode() { return code; }
}
kotlin 复制代码
// Kotlin: enum class
enum class Direction(val degrees: Double) {
    NORTH(0.0), EAST(90.0), SOUTH(180.0), WEST(270.0);

    fun toRadians() = Math.toRadians(degrees)
}

// 使用
val d = Direction.NORTH
println(d.name)         // "NORTH"
println(d.ordinal)      // 0

Python 实现

python 复制代码
from enum import Enum, IntEnum, Flag, IntFlag, auto

# === 基本枚举 ===

class Direction(Enum):
    NORTH = "N"
    SOUTH = "S"
    EAST = "E"
    WEST = "W"

# 访问
print(Direction.NORTH)              # Direction.NORTH
print(Direction.NORTH.value)        # 'N'
print(Direction.NORTH.name)         # 'NORTH'

# 比较 --- 枚举用 is 或 == 比较,不支持 < >
print(Direction.NORTH is Direction.NORTH)   # True
print(Direction.NORTH == Direction.NORTH)  # True
# Direction.NORTH < Direction.SOUTH         # TypeError

# 遍历
for d in Direction:
    print(f"{d.name} = {d.value}")

# 按值查找
d = Direction("N")          # Direction.NORTH
d = Direction["NORTH"]      # Direction.NORTH

# === 带方法和属性的枚举 ===

class Status(Enum):
    OK = 200
    NOT_FOUND = 404
    ERROR = 500

    @property
    def is_success(self):
        return self.value < 400

    def describe(self):
        return f"{self.name} (HTTP {self.value})"

print(Status.OK.is_success)          # True
print(Status.ERROR.describe())       # "ERROR (HTTP 500)"

# === auto(): 自动赋值 ===

class Color(Enum):
    RED = auto()
    GREEN = auto()
    BLUE = auto()

print(list(Color))                   # [Color.RED, Color.GREEN, Color.BLUE]
print(Color.RED.value)               # 1(从1开始递增)

# 自定义 auto 行为
class Planet(Enum):
    MERCURY = auto()
    VENUS = auto()
    EARTH = auto()

    def _generate_next_value_(name, start, count, last_values):
        return name.capitalize()

print(Planet.EARTH.value)            # 'Earth'

# === IntEnum: 值为整数的枚举 ===
# 可以和整数直接比较和运算

class Priority(IntEnum):
    LOW = 1
    MEDIUM = 2
    HIGH = 3

print(Priority.HIGH > Priority.LOW)  # True --- 支持比较
print(Priority.MEDIUM == 2)          # True --- 可以和 int 比较

# === Flag [3.6+] 和 IntFlag [3.6+]: 位标志枚举 ===

class Permission(Flag):
    READ = auto()        # 1
    WRITE = auto()       # 2
    EXECUTE = auto()     # 4

# 位运算
p = Permission.READ | Permission.WRITE
print(p)                          # Permission.READ|WRITE
print(Permission.READ in p)       # True
print(Permission.EXECUTE in p)    # False
print(p & Permission.READ)        # Permission.READ --- 检查标志

# IntFlag: 可以和整数互操作
class Mode(IntFlag):
    R = 1
    W = 2
    X = 4

m = Mode.R | Mode.W
print(m)                          # Mode.R|W
print(m == 3)                     # True --- 和整数比较
print(m | Mode.X)                 # Mode.R|W|X
print(m & 3)                      # Mode.R|W

# === 枚举的实用模式 ===

# 1. 枚举映射到元数据
class HTTPStatus(Enum):
    OK = (200, "OK")
    NOT_FOUND = (404, "Not Found")
    ERROR = (500, "Internal Server Error")

    def __init__(self, code, reason):
        self.code = code
        self.reason = reason

print(HTTPStatus.OK.code)          # 200
print(HTTPStatus.OK.reason)        # "OK"

# 2. 枚举作为函数参数(类型安全)
def handle_status(status: HTTPStatus):
    if status == HTTPStatus.OK:
        return "Success"
    return f"Error: {status.reason}"

# 3. 枚举成员唯一性保证
class Color(Enum):
    RED = 1
    CRIMSON = 1    # 这是 RED 的别名,不是新成员

print(Color.RED is Color.CRIMSON)  # True --- 同一个对象
print(list(Color))                 # [Color.RED] --- 别名不出现在遍历中
print(Color.CRIMSON)               # Color.RED --- 显示规范名

核心差异

特性 Java enum Kotlin enum class Python Enum
定义 enum Direction { NORTH } enum class Direction { NORTH } class Direction(Enum): NORTH = "N"
序数 ordinal 构造参数 显式赋值或 auto()
比较 可用 == 可用 == is==,不支持 <(IntEnum 除外)
位标志 EnumSet 无原生支持 Flag / IntFlag
别名 支持(同名值自动别名)
方法 完整支持 完整支持 支持 @property 和方法
类型安全 编译期 编译期 运行时(mypy 可检查)

常见陷阱

python 复制代码
# 陷阱1: 枚举值不一定要是整数
class BadHabit(Enum):
    A = "hello"          # 合法
    B = [1, 2]           # 合法(但不推荐)
    C = {"key": "val"}   # 合法(但不推荐)

# 陷阱2: 枚举不是整数(除非用 IntEnum)
class Status(Enum):
    OK = 200

# Status.OK == 200       # False!
# Status.OK < Status.ERROR  # TypeError
# 用 IntEnum:
class Status(IntEnum):
    OK = 200
    ERROR = 500
print(Status.OK == 200)  # True

# 陷阱3: 不要用 is 比较值
print(Status.OK.value is 200)  # 不保证 True(小整数缓存机制)
print(Status.OK.value == 200)  # 正确

# 陷阱4: 枚举不能实例化
# Status(201)            # ValueError: 201 is not a valid Status
# 用 Status._value2member_map_ 检查合法值

何时使用

  • Enum: 有限状态集合、配置选项、错误码------任何需要"有限命名常量"的场景。
  • IntEnum: 需要和整数互操作时(如协议码、优先级)。
  • Flag/IntFlag: 权限、选项组合------需要位运算的场景。
  • 不用枚举: 值集合不固定时用常量模块或配置文件。

3.10 typing 模块核心类型

Java/Kotlin 的类型系统是编译期强制的。Python 的类型注解是提示,运行时默认不检查。理解这一点是关键。

Java/Kotlin 对比

java 复制代码
// Java: 类型是强制的
List<String> names = new ArrayList<>();
names.add("Alice");
// names.add(42);        // 编译错误

Map<String, Integer> map = new HashMap<>();
Optional<String> maybe = Optional.ofNullable(null);

// Java: 泛型
public <T extends Comparable<T>> T max(List<T> list) { ... }
kotlin 复制代码
// Kotlin: 类型系统更灵活
val names: List<String> = listOf("Alice")
val nullable: String? = null          // 可空类型
val result: Int? = nullable?.length   // 安全调用

// Kotlin: 泛型
fun <T : Comparable<T>> max(list: List<T>): T { ... }

Python 实现

python 复制代码
from typing import (
    List, Dict, Set, Tuple,
    Optional, Union, Literal,
    Callable, Any, NoReturn,
    TypeAlias
)

# === 基本容器类型注解 ===

names: List[str] = ["Alice", "Bob"]
scores: Dict[str, int] = {"Alice": 90, "Bob": 85}
unique: Set[int] = {1, 2, 3}
point: Tuple[int, int] = (1, 2)

# 嵌套
matrix: List[List[int]] = [[1, 2], [3, 4]]
tree: Dict[str, List[str]] = {
    "fruits": ["apple", "banana"],
    "veggies": ["carrot"]
}

# [3.9+] 可以直接用内置类型(不需要从 typing 导入)
names: list[str] = ["Alice", "Bob"]       # Python 3.9+
scores: dict[str, int] = {"Alice": 90}    # Python 3.9+
point: tuple[int, int] = (1, 2)           # Python 3.9+

# === Optional: 可空类型 ===
# Optional[X] 等价于 Union[X, None]
# 类似 Kotlin 的 String?

from typing import Optional

def find_user(user_id: int) -> Optional[str]:
    """找不到返回 None"""
    if user_id == 1:
        return "Alice"
    return None

name: Optional[str] = find_user(1)
if name is not None:
    print(name.upper())        # 类型检查器知道这里 name 不是 None

# === Union: 多种类型 ===

from typing import Union

def process(value: Union[int, str]) -> str:
    if isinstance(value, int):
        return f"Number: {value}"
    return f"String: {value}"

# [3.10+] 用 | 语法代替 Union
def process(value: int | str) -> str:    # Python 3.10+
    if isinstance(value, int):
        return f"Number: {value}"
    return f"String: {value}"

# === Literal: 限定具体值 ===
# 类似枚举,但更轻量

from typing import Literal

def set_level(level: Literal["debug", "info", "warn", "error"]) -> None:
    print(f"Level: {level}")

set_level("debug")          # OK
# set_level("verbose")      # mypy 报错

# [3.8+] 也可以用 Literal[int] 限定具体数字
def http_get(url: str, status_code: Literal[200, 301, 404]) -> str:
    ...

# === Callable: 函数类型 ===

from typing import Callable

# Callable[[参数类型...], 返回类型]
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
    return func(a, b)

result = apply(lambda x, y: x + y, 1, 2)  # 3

# 更复杂的函数签名
from typing import Protocol

class Handler(Protocol):
    def __call__(self, request: str) -> dict: ...

def use_handler(handler: Handler) -> None:
    result = handler("test")

# === Any 和 NoReturn ===

from typing import Any, NoReturn

def process(data: Any) -> None:
    """接受任意类型"""
    pass

def fail(message: str) -> NoReturn:
    """永远不会正常返回"""
    raise ValueError(message)

# === TypeAlias: 类型别名 ===

# [3.12+] 用 type 语句
# type Vector = list[float]
# type Matrix = list[Vector]

# [3.10-3.11] 用 TypeAlias
from typing import TypeAlias

Vector: TypeAlias = list[float]
Matrix: TypeAlias = list[Vector]
UserId: TypeAlias = int
Config: TypeAlias = dict[str, Any]

def dot_product(a: Vector, b: Vector) -> float:
    return sum(x * y for x, y in zip(a, b))

# === 类型注解是提示,不是约束 ===

# 这段代码可以运行,但 mypy 会报错
names: list[str] = ["Alice", "Bob"]
names.append(42)             # 运行正常!mypy 报错: List item 0 has incompatible type "int"

def greet(name: str) -> str:
    return f"Hello, {name}"

greet(42)                    # 运行正常!mypy 报错: Argument 1 has incompatible type "int"

# Python 运行时完全忽略类型注解
# 类型注解的价值:
# 1. IDE 自动补全和重构
# 2. mypy/pyright 静态检查
# 3. 文档化代码意图
# 4. 团队协作契约

# === 完整示例: 带完整类型注解的函数 ===

from typing import Optional

def search_users(
    name: Optional[str] = None,
    min_age: int = 0,
    max_age: int = 150,
    tags: list[str] | None = None,
) -> list[dict[str, Any]]:
    """搜索用户

    Args:
        name: 用户名(模糊匹配),None 表示不限制
        min_age: 最小年龄
        max_age: 最大年龄
        tags: 标签列表,None 表示不限制

    Returns:
        匹配的用户列表
    """
    if tags is None:
        tags = []
    # ... 实际搜索逻辑
    return [{"name": "Alice", "age": 30, "tags": ["dev"]}]

核心差异

特性 Java/Kotlin Python typing
类型检查 编译期强制 运行时忽略,mypy 可选检查
可空类型 String? / String Optional[str] / `str
联合类型 无(用继承或重载) Union[int, str] / `int
字面量类型 Literal["a", "b"]
函数类型 lambda 类型推断 Callable[[int], str]
泛型 List<T> list[T](Python 3.9+)
类型别名 typealias type X = ...(3.12+)或 TypeAlias
不可达 NoReturn

常见陷阱

python 复制代码
# 陷阱1: Optional[str] 不意味着值一定有 .upper() 方法
def get_name() -> Optional[str]:
    return None

name = get_name()
# name.upper()              # 运行时: AttributeError --- mypy 也会报错
if name is not None:
    name.upper()            # 安全 --- mypy 知道这里不是 None

# 陷阱2: list[str] 不阻止你放入 int
# Python 运行时不检查,类型注解只是提示
# 要运行时检查,需要 pydantic 或手动 isinstance

# 陷阱3: Union 的顺序不影响行为
def f(x: int | str) -> str:     # 和 str | int 完全等价
    return str(x)

# 陷阱4: typing 模块的类型 vs 内置类型
# Python 3.9+: list[str] == List[str](等价)
# Python 3.8:  只能用 List[str](从 typing 导入)
# 推荐: 3.9+ 直接用 list[str],更简洁

# 陷阱5: Callable 的参数列表不能表达关键字参数
# Callable[[int, str], bool] 只表达位置参数
# 需要精确签名时用 Protocol

何时使用

  • 始终加类型注解: 函数参数和返回值------这是现代 Python 的最佳实践,即使运行时不检查。
  • 复杂项目用 mypy : mypy --strict your_project/ 可以发现大量潜在 bug。
  • Optional vs 不加 : 参数可能为 None 时必须用 Optional;不会为 None 时不要加。
  • Literal vs Enum : 少量固定值用 Literal;需要方法和逻辑时用 Enum
  • Any : 尽量避免。Any 会关闭该值的类型检查,相当于"逃逸"了类型系统。

本章速查表

Python 类型 JVM 对应 不可变版本 推导式 核心操作
list ArrayList/MutableList tuple [x for x in ...] append, extend, sort,切片
tuple record/Pair 自身 解包, 索引, 作为 dict key
dict HashMap/Map MappingProxyType {k:v for ...} get, setdefault, `
set HashSet/Set frozenset {x for x in ...} `
deque ArrayDeque appendleft, popleft, rotate
Counter Guava Multiset most_common, `+&-
defaultdict computeIfAbsent 自动初始化缺失 key
@dataclass data class/record frozen=True replace, field()
NamedTuple data class 自身 解包, _asdict, _replace
Enum enum/enum class 自身 value, name, Flag 位运算
相关推荐
HaiXCoder5 小时前
python从入门到精通-第0章: 思维模式碰撞
python
Orange_sparkle5 小时前
learn claude code学习记录-S02
java·python·学习
小郑加油5 小时前
python学习Day1:python的安装与环境搭载
python·学习·小白记录,保姆式教程
Zewen PAN6 小时前
wsl安装pytorch
人工智能·pytorch·python
aq55356006 小时前
四大编程语言对比:PHP、Python、Java、易语言
java·python·php
qq_283720056 小时前
Python GIL 底层实现与高并发突破实战
python·性能优化·高并发·全局锁
橙露6 小时前
Python 对接 API:自动化拉取、清洗、入库一站式教程
开发语言·python·自动化
Omigeq6 小时前
1.4 - 曲线生成轨迹优化算法(以BSpline和ReedsShepp为例) - Python运动规划库教程(Python Motion Planning)
开发语言·人工智能·python·算法·机器人
2301_808414386 小时前
自动化测试的实施
开发语言·python