上篇回顾 :3.1-01 讲了 Python 虚拟环境的必要性(与 Java Maven 依赖机制对比)+ uv/pyproject.toml 现代工具链。本篇进入 Python 基础语法------对学完 Java 的你来说,Python 语法本身不难,难的是写出 Pythonic 的代码而不是「用 Python 写 Java」。

一、开篇:用 Python 写 Java 的典型症状
学完 Java 转 Python,最容易写出这种代码:
python
# Java 风格的 Python(能跑但不 Pythonic)
words = ["hello", "world", "python"]
result = []
for i in range(len(words)):
result.append(words[i].upper())
print(result)
Pythonic 的写法:
python
words = ["hello", "world", "python"]
result = [w.upper() for w in words]
print(result)
同样是遍历转大写,Pythonic 版本用推导式一行搞定。本篇把 Python 四大容器、推导式、函数一等公民、异常哲学、鸭子类型讲透,帮你从「用 Python 写 Java」升级到「写 Pythonic 代码」。
二、四大数据容器
2.1 对照表
| Python | Java 对应 | 可变 | 有序 | 重复 |
|---|---|---|---|---|
| list | ArrayList | ✅ | ✅ | ✅ |
| dict | HashMap | ✅ | ✅(3.7+) | 键不重复 |
| tuple | (无直接对应) | ❌ | ✅ | ✅ |
| set | HashSet | ✅ | ❌ | ❌ |
2.2 list 列表
python
# 创建
nums = [1, 2, 3, 4, 5]
# 索引(支持负数)
nums[0] # 1
nums[-1] # 5(最后一个)
# 切片
nums[1:3] # [2, 3]
nums[:3] # [1, 2, 3]
nums[::2] # [1, 3, 5](步长 2)
# 增删
nums.append(6)
nums.insert(0, 0)
nums.pop() # 删除并返回最后一个
nums.remove(3) # 删除第一个值为 3 的元素
2.3 dict 字典
python
# 创建
user = {"name": "Tom", "age": 18}
# 访问
user["name"] # "Tom"
user.get("email", "") # 不存在返回默认值
# 增改
user["email"] = "tom@example.com"
user.update({"age": 20, "phone": "123"})
# 遍历
for key, value in user.items():
print(key, value)
2.4 tuple 元组
python
# 创建
point = (3, 4)
single = (1,) # 单元素要加逗号
# 不可变
point[0] = 5 # TypeError!
# 用途
def get_user():
return "Tom", 18 # 返回元组(省略括号)
name, age = get_user() # 解构
2.5 set 集合
python
# 创建
nums = {1, 2, 3, 2} # {1, 2, 3}(自动去重)
# 运算
a = {1, 2, 3}
b = {2, 3, 4}
a & b # {2, 3} 交集
a | b # {1, 2, 3, 4} 并集
a - b # {1} 差集
2.6 容器选型决策
| 需求 | 选 |
|---|---|
| 有序、可变、可重复 | list |
| 键值对 | dict |
| 不可变、有序 | tuple |
| 去重 | set |
| 有序去重 | dict(Python 3.7+ 保序) |
三、切片:Python 的优雅利器
python
s = "Hello, World"
s[0:5] # "Hello"
s[7:] # "World"
s[-5:] # "World"
s[::-1] # "dlroW ,olleH"(反转)
s[::2] # "HloWrd"(每隔一个字符)
切片对 list/tuple/str/bytes 都适用。Java 没有等价语法(要写循环或 substring 拼接)。
四、推导式:Pythonic 的灵魂
4.1 列表推导式
python
# 传统
squares = []
for i in range(10):
squares.append(i ** 2)
# 推导式
squares = [i ** 2 for i in range(10)]
# 带条件
evens = [i for i in range(10) if i % 2 == 0]
# 嵌套
matrix = [[i * j for j in range(3)] for i in range(3)]
4.2 字典/集合推导式
python
# 字典
word_len = {w: len(w) for w in ["hello", "world", "py"]}
# 集合
unique_lens = {len(w) for w in words}
4.3 生成器表达式(惰性)
python
# 列表推导式:立即生成(占内存)
squares = [i ** 2 for i in range(1000000)] # ~8MB
# 生成器表达式:惰性生成(不占内存)
squares_gen = (i ** 2 for i in range(1000000)) # ~100B
sum(squares_gen) # 按需计算
大数据集用生成器表达式,避免内存爆炸。
五、函数一等公民
5.1 函数可以赋值、传递、返回
python
def greet(name):
return f"Hello, {name}"
# 赋值
say_hello = greet
say_hello("Tom") # "Hello, Tom"
# 传递
def apply(func, value):
return func(value)
apply(greet, "Tom")
# 返回(高阶函数)
def make_multiplier(n):
def multiply(x):
return x * n
return multiply
double = make_multiplier(2)
double(5) # 10
5.2 lambda 匿名函数
python
# 排序
users = [{"name": "Tom", "age": 20}, {"name": "Jerry", "age": 18}]
sorted(users, key=lambda u: u["age"]) # 按 age 排序
# map/filter
nums = [1, 2, 3, 4, 5]
list(map(lambda x: x ** 2, nums)) # [1, 4, 9, 16, 25]
list(filter(lambda x: x % 2 == 0, nums)) # [2, 4]
但 Pythonic 更推荐用推导式替代 map/filter:
python
[x ** 2 for x in nums] # 替代 map
[x for x in nums if x % 2 == 0] # 替代 filter
六、作用域 LEGB
Python 查找变量的顺序:L → E → G → B
| 层 | 说明 |
|---|---|
| L (Local) | 函数内局部 |
| E (Enclosing) | 外层嵌套函数 |
| G (Global) | 模块全局 |
| B (Built-in) | 内置(len/range/print) |
python
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # local
inner()
print(x) # enclosing
七、异常处理哲学:EAFP vs LBYL
7.1 两种风格
| 风格 | 全称 | 含义 |
|---|---|---|
| EAFP | Easier to Ask Forgiveness than Permission | 先做,出错再处理 |
| LBYL | Look Before You Leap | 先检查,再做 |
7.2 Java 是 LBYL
java
if (map.containsKey(key)) {
return map.get(key);
}
7.3 Python 推荐 EAFP
python
try:
return map[key]
except KeyError:
return None
为什么 Python 推荐 EAFP:
- 避免 TOCTOU(检查与使用之间的竞态)
- 性能更好(异常只在出错时才有开销,检查每次都要执行)
- 更 Pythonic
八、鸭子类型
8.1 什么是鸭子类型
如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子。
Python 不关心对象的类型,只关心它有没有需要的方法/属性。
python
def make_sound(animal):
animal.speak() # 不检查类型,只要有 speak 方法
class Dog:
def speak(self): print("Woof")
class Cat:
def speak(self): print("Meow")
make_sound(Dog()) # Woof
make_sound(Cat()) # Meow
8.2 与 Java 接口的对比
Java 要定义接口、显式 implements:
java
interface Animal { void speak(); }
class Dog implements Animal { public void speak() { ... } }
Python 不需要接口------有方法就行。更灵活但少了编译时检查。
8.3 type hints 弥补类型安全
python
from typing import Protocol
class Animal(Protocol):
def speak(self) -> str: ...
def make_sound(animal: Animal) -> None:
animal.speak()
class Dog:
def speak(self) -> str: return "Woof"
make_sound(Dog()) # mypy 检查通过
make_sound("string") # mypy 报错(没有 speak 方法)
Protocol 是 Python 3.8+ 的结构化类型------不要求显式继承,只要有对应方法就算符合。配合 mypy 做静态检查。
九、动态类型的灵活性什么时候变成灾难
9.1 灵活的场景
- 快速原型开发
- 数据处理脚本(pandas/numpy)
- 自动化运维
- Web 后端(FastAPI/Flask)
9.2 灾难的场景
- 大型团队协作(类型不明确导致误解)
- 长期维护的项目(重构时没有类型保护)
- 复杂业务逻辑(类型错误运行时才暴露)
9.3 type hints + mypy 能救回静态世界吗
部分能------type hints 是可选的,mypy 是额外工具。不会像 Java 那样强制。但加上 type hints 后:
- IDE 补全更智能
- 重构更安全
- 团队协作更清晰
推荐:项目用 type hints,但不过度追求 100% 类型覆盖。核心接口和数据模型必须标注,脚本和小工具可以省略。
十、小结表
| 概念 | Python | Java 对照 |
|---|---|---|
| 四大容器 | list/dict/tuple/set | ArrayList/HashMap/HashSet |
| 切片 | s1:3 | substring/Arrays.copyOfRange |
| 推导式 | x for x in nums | Stream API |
| 函数一等公民 | 可赋值传递返回 | Lambda/函数式接口 |
| 异常哲学 | EAFP | LBYL |
| 鸭子类型 | 有方法就行 | 显式 implements |