第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 位运算