/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
第 1 章 心智转变与现代工程底座
1.1 动态类型 + 类型注解
最本质的区别:Java 是静态强类型,Python 是动态类型 + 可选类型注解。
# Python:变量只是"名字贴在对象上",可以随时换类型(不推荐,但合法)
x = 42 # x 是 int
x = "hello" # 现在是 str,运行时不报错
// Java:类型在编译期绑定,换了直接编译失败
int x = 42;
x = "hello"; // 编译错误
关键认知:
- Python 3.5+ 支持类型注解(type hints),但它只是"提示":
- IDE 和静态检查器(mypy / pyright)在静态阶段据此进行类型推导与错误拦截;
- 运行时解释器完全不校验——给注解为 int 的参数传字符串,解释器不会报错。
- 现代 Python 项目(尤其 agent 框架:pydantic、FastAPI、LangGraph)默认全量使用类型注解,建议在所有函数签名中规范标注类型注解。
- 例外:pydantic 模型和 FastAPI 会在运行时真正读取并校验注解——这是框架行为,不是语言行为。
# ': int' 与 '-> int' 即类型注解,等价 Java 形参/返回值类型声明(后置且运行时不强制校验)
def add(a: int, b: int) –> int:
return a + b
add("a", "b") # 返回 "ab":类型不符但运行不报错,IDE / pyright 会标红
add(1, "hello") # 报错 TypeError:强类型语言不支持 int + str(报错由加法运算触发,非注解拦截)
1.2 一切皆对象、没有 new、缩进即语法块
# 函数、类、模块本身都是对象,可以赋值给变量、当参数传(Java 8 之前做不到)
def greet(name: str) –> str:
return f"hello, {name}"
f = greet # 函数赋值给变量
f("world") # "hello, world"
# 没有 new
class Agent:
def __init__(self, name: str): # 构造逻辑写在 __init__
self.name = name
a = Agent("bot") # 直接类名调用,没有 new 关键字
缩进即语法块——Python 用缩进代替 {}。标准是 4 个空格,绝不用 Tab(ruff/formatter 会强制统一)。Java 程序员最需要防的是混用空格数导致逻辑错误:
def check(x: int) –> str:
if x > 0:
return "positive"
return "non-positive" # 缩进层级决定它属于 if 还是函数
1.3 项目结构对照
Maven 项目 Python 项目(现代 src 布局)
├── pom.xml ├── pyproject.toml ← 项目元数据+依赖声明
├── src/main/java/com/foo/ ├── src/foo/
│ ├── Main.java │ ├── __init__.py ← 标记这是包
│ ├── service/ │ ├── service/
│ │ └── UserService.java │ │ ├── __init__.py
│ │ │ │ └── user_service.py ← 一个文件=一个模块
│ └── model/ │ └── model/
│ └── User.java │ └── user.py
└── src/test/java/ └── tests/ ← 测试通常在 src 外
└── UserServiceTest.java └── test_user_service.py
对应关系:
| pom.xml 依赖声明 | pyproject.toml 的 [project] dependencies |
| groupId:artifactId | 包名(PyPI 上的名字) |
| mvn install 本地仓库 | uv/pip 安装到虚拟环境 |
| src/main/java | src/<包名>/ |
| src/test/java | tests/ |
1.4 依赖与虚拟环境:uv / pip
Java 的依赖装进本地全局仓库(~/.m2),所有项目共享。Python 不同:每个项目必须有自己独立的虚拟环境(一个装了该项目所有依赖的隔离目录),否则不同项目的依赖版本会互相冲突。
当前事实标准是 uv(Rust 编写,极快),替代了老的 pip + venv + pip-tools 组合:
# 创建项目(生成 pyproject.toml + .venv 虚拟环境)
uv init my-agent && cd my-agent
# 添加依赖(自动创建/更新虚拟环境,等价于在 pom.xml 加依赖并 mvn install)
uv add fastapi uvicorn httpx pydantic
uv add "pydantic>=2.0"
# 开发依赖(相当于 Maven 的 scope=test/provided)
uv add –dev pytest ruff
# 运行(uv 自动找到项目虚拟环境)
uv run python -m my_agent.main
uv run pytest
# 锁定文件 uv.lock ↔ Maven 的依赖锁定,保证团队可复现
uv lock
类比记忆:uv ≈ mvn + mvnw 合体,uv.lock ≈ 锁定版本的 pom.xml,.venv/ ≈ 每个 project 私有的轻量 ~/.m2。
1.5 模块与包:import 机制
Java 的包名与目录强制一致;Python 的 import 路径就是文件系统路径:
# src/my_agent/tools/search.py 内有函数 web_search
from my_agent.tools.search import web_search # 全路径导入
from my_agent.tools import search # 导入模块,用 search.web_search()
import my_agent.tools.search as search_mod # 起别名
# my_agent/tools/__init__.py 里可以做"包门面":
# from .search import web_search ← 让外部直接 from my_agent.tools import web_search
规则要点:
- 一个 .py 文件 = 一个模块(module);含 __init__.py 的目录 = 一个包(package)。__init__.py 可以为空,也可以写导出门面。
- 相对导入 from .search import x(同包)、from ..core import y(上一级包)——只在包内部用。
- 没有访问修饰符:所有顶层名字默认可导入,_ 前缀只是"别导我"的约定。
- 循环导入(circular import)↔ Java 循环依赖:Python 会运行时报错,靠拆分模块/延迟导入解决。
- Python 3.3+ 有命名空间包,但项目内统一写 __init__.py 更规范。
运行入口与 sys.path 陷阱
在 Java 中,类的定位由 Classpath 统一托管,类名与目录强绑定;而在 Python 中,模块寻找依赖解释器的 sys.path 路径列表。
# 错误示例:子目录直接运行报错
# 目录为 src/my_agent/tools/search.py,其内部写了:
from my_agent.core import config
如果直接在终端运行 python src/my_agent/tools/search.py,会立即报错:
ModuleNotFoundError: No module named 'my_agent'。
底层机制:
-m 会强制将当前运行的**工作目录(项目根目录)**加入 sys.path,使全路径导入正常解析。
入口守护:if __name__ == "__main__": 的底层原理:
Java 每个应用有明确的 public static void main 入口类;Python 任何 .py 文件既能作为入口执行,也能作为模块被导入:
- 当该文件被直接运行时,Python 解释器自动将其内置变量 __name__ 赋值为 "__main__";
- 当该文件被其他模块 import 时,__name__ 会被赋值为其真实的模块路径名(如 "my_agent.tools.search")。
- 因此,if __name__ == "__main__": 保证了该模块作为库被其他代码引用时,不会意外触发测试或初始化脚本。
1.6 代码规范:ruff
一个工具覆盖 Java 里 Checkstyle + Spotless + PMD 的职责:
uv run ruff check . –fix # lint 并自动修复(未使用导入、错误写法…)
uv run ruff format . # 格式化 ↔ google-java-format
CI 里两条命令跑一遍即可,配置全在 pyproject.toml。配置 pre-commit hook(pre-commit 框架)可在代码提交时自动执行,对齐 Git Hooks 校验规范。
1.7 严格类型检查工作流:pyright
推荐的工程组合(写进 pyproject.toml 一次,之后无感):
[tool.ruff] # lint + format 二合一(替代 black/isort/flake8)
line-length = 100
[tool.pyright]
typeCheckingMode = "strict" # 严格模式,接近 Java 的编译期检查体验
uv run ruff check . && uv run ruff format . # 提交前
uv run pyright # CI 中
记住定位:注解 = 文档 + IDE 补全 + 静态抓错 + 框架元数据,四重收益,成本几乎为零,所以 Python 社区全注解是主流。
第 2 章 语法基石与避坑
2.1 变量与基本类型
| int(8/16/32/64位) | int | Python 整数无限精度,没有溢出 |
| long | int | 同上 |
| double / float | float | 64 位双精度,等价 Java double |
| 无 | complex | 复数,后端开发基本不用 |
| boolean | bool | True / False(首字母大写) |
| char | 无 | 单字符就是长度为 1 的 str |
| String | str | 不可变,Unicode,无需 char 处理编码烦恼 |
| null | None | 且 None 只能和 None 比较 |
i = 2 ** 100 # 1267650600228229401496703205376,整数不溢出
f = 0.1 + 0.2 # 0.30000000000000004,和 Java 一样有浮点误差
s = None # Java 的 null
flag = True # 不是 true
# None 判断惯用法:用 is,不用 ==
if s is None: ...
if s is not None: ...
# 交换变量不需要临时变量
a, b = b, a
# 多重赋值 / 解包(Java 没有)
x, y, z = [1, 2, 3] # x=1, y=2, z=3
first, *rest = [1, 2, 3, 4] # first=1, rest=[2,3,4]
字符串:不可变 + 自动 Unicode,拼接推荐 f-string(见 2.5)。与 Java 不同,没有 StringBuilder 的必要,大量拼接用 "".join(list):
parts = ["SELECT *", "FROM users", "WHERE id = :id"]
sql = "\\n".join(parts) # 等价 String.join,sql="SELECT *\\nFROM users\\nWHERE id = :id"
内存传递模型:共享对象引用与深浅拷贝
Java 是严格的值传递(基本类型传数值副本,引用类型传引用地址的副本);Python 采用 Pass-by-assignment(对象引用共享传递):
# 1. 不可变对象(int, float, str, tuple):修改即创建新对象
def increment(val: int):
val += 1 # val 指向了全新的 int 对象,外层不受影响
x = 10
increment(x)
print(x) # 仍为 10
# 2. 可变对象(list, dict, set, 实例对象):原地变更直接影响外部
def register_tool(registry: dict, name: str):
registry[name] = True # 原地修改字典
reg = {}
register_tool(reg, "search")
print(reg) # {'search': True} —— 外部字典被修改!
浅拷贝 vs 深拷贝陷阱:
切片 nums[:]、dict.copy() 或 list.copy() 仅为浅拷贝(Shallow Copy)。如果容器内嵌套了可变对象(如列表里存字典),修改内层对象依然会串扰原数据:
import copy
tools = [{"name": "search", "timeout": 30}]
shallow = tools.copy() # 浅拷贝
deep = copy.deepcopy(tools) # 深拷贝
shallow[0]["timeout"] = 60 # tools[0]["timeout"] 也会被改成 60!
deep[0]["timeout"] = 99 # tools[0]["timeout"] 依然是 60,互不干扰
在 Agent 状态流转或配置复制场景中,涉及嵌套配置时必须使用 copy.deepcopy。
2.2 容器:list / dict / set / tuple
这是 Python 最常用的容器,没有 ArrayList/HashMap 的泛型语法,直接字面量创建:
nums: list[int] = [1, 2, 3] # ↔ ArrayList<Integer>
user: dict[str, str] = {"name": "tom", "role": "admin"} # ↔ HashMap<String,String>
tags: set[str] = {"a", "b"} # ↔ HashSet<String>
point: tuple[int, int] = (3, 4) # 不可变元组,Java 无直接对应(record 的简化版)
nums.append(4) # add
nums.extend([5, 6]) # addAll
nums.pop() # remove 尾元素
nums.remove(2) # 按值删除(不存在则抛 ValueError)
len(nums) # size(),函数而非方法
2 in nums # contains(),惯用 in
字典是 Python 最核心的容器之一,常用于消息历史、工具注册表和配置传递:
user = {"name": "tom", "role": "admin"}
user["name"] # get,KeyError 若不存在
user.get("email") # ↔ getOrDefault 语义:不存在返回 None
user.get("email", "") # 不存在返回默认值
user["email"] = "a@b.com" # put
del user["email"] # remove
user.pop("role") # 删除并返回值
# 遍历(Java: for (var e : map.entrySet()))
for key, value in user.items():
print(key, value)
for key in user: # 默认遍历 key(Java: keySet())
...
# 合并字典(Python 3.9+)
merged = {**defaults, **overrides} # ↔ putAll
merged = defaults | overrides
# 字典推导式(见 2.6)
{u["name"]: u for u in users}
list 与 Java List 的关键差异:切片
nums = [0, 1, 2, 3, 4, 5]
nums[1:4] # [1, 2, 3] 下标 [1,4) —— 常用于消息历史截断
nums[:3] # [0, 1, 2] 前 3 个
nums[–3:] # [3, 4, 5] 后 3 个(agent 里"保留最近 N 条消息"就这么写)
nums[::2] # [0, 2, 4] 隔一个取一个,步长为 2
nums[::–1] # 反转
nums[:3] = [9] # 切片赋值
切片是副本(浅拷贝),修改它不影响原 list——和 Java subList 返回视图不同,这是常见坑。
tuple 的用途:函数返回多个值、字典键、不可变数据:
def parse(text: str) –> tuple[str, int]:
return "tom", 18 # 多返回值
name, age = parse("…") # 调用处解包
选型对照表
| 有序列表 | ArrayList | list |
| 键值映射 | HashMap | dict(3.7+ 保证插入序) |
| 去重集合 | HashSet | set |
| 不可变列表 | List.of(…) | tuple |
| 有序去重 | LinkedHashSet | dict.fromkeys(seq) |
| 线程安全队列 | BlockingQueue | queue.Queue / asyncio.Queue |
2.3 控制流差异
# if / elif / else(Java 的 else if → elif)
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
else:
grade = "C"
# 三元表达式:值在前,条件在后(顺序和 Java 相反)
level = "admin" if user["role"] == "admin" else "user"
# Python 3.10+ 的 match-case ↔ switch(带模式匹配,比 Java switch 强大)
def handle(event: dict) –> str:
match event:
case {"type": "message", "content": c}:
return f"msg: {c}"
case {"type": "quit"}:
return "bye"
case _:
return "unknown"
# while 与 Java 相同;没有 do-while
# for 是 for-each,没有 Java 式的下标 for
for item in items: # ↔ for (var item : items)
print(item)
# 需要下标时用 enumerate ↔ 手动维护 i
for i, item in enumerate(items): # ↔ IntStream.range
print(i, item)
# 并行遍历两个集合:zip ↔ 手动对齐下标
for name, age in zip(names, ages):
print(name, age)
# range ↔ IntStream.rangeClosed(1, 10)
for i in range(1, 11): # [1, 11),步长默认 1
...
for i in range(10, 0, –1): # 倒序
# for-else:循环正常结束(没被 break)才执行 else —— Java 没有
for tool in tools:
if tool.name == target:
break
else:
raise ToolNotFound(target)
真值判断
Python 中任何对象都能直接放在 if 里判断:
if items: # 非空 list → True(Java 必须写 !items.isEmpty())
if name: # 非空字符串 → True
if errors: # 非空 dict/set → True
if not items: ... # 空集合
# 注意:判断"是 None"用 is None;判断"是空"用真值。两者不同!
msg = "" # 空字符串
if msg: # False —— 会把空串当"没有"
if msg is None: # False —— 空串不是 None
逻辑运算符是单词:and / or / not ↔ && / || / !,且 and/or 返回操作数本身(短路求值返回值):
name = input_name or "anonymous" # ↔ Optional/default 惯用法
2.4 函数
# 基本定义(注解强烈建议写)
def call_llm(prompt: str, temperature: float = 0.7, model: str = "gpt-4o") –> str:
...
# Python 没有重载!用默认参数替代:
call_llm("hi")
call_llm("hi", 0.2)
call_llm("hi", model="deepseek-chat") # 关键字参数,Java 没有
# *args / **kwargs:可变参数 ↔ Java 的 T… args
def log(level: str, *parts: str, **context: str) –> None:
print(level, *parts, context)
log("INFO", "request done", user_id="u1", trace_id="t1")
要点:
- 没有重载。默认参数 + 关键字参数是 Python 对重载的替代,agent 工具定义里大量使用。
- *parts 收集所有位置参数为 tuple;**context 收集所有关键字参数为 dict。
- 关键字参数让调用处自文档化,比 Java 传一堆位置参数可读性好得多。
默认参数的可变对象陷阱
在 Java 中,方法重载或默认处理是在调用时动态执行;而在 Python 中:
函数的默认参数在模块被加载(函数定义解析)时仅求值一次,并持久保存在函数对象的 __defaults__ 元组中!
# 危险代码:默认参数使用了可变对象 list
def append_tool(tool_name: str, tools: list[str] = []) –> list[str]:
tools.append(tool_name)
return tools
print(append_tool("search")) # ['search']
print(append_tool("calc")) # ['search', 'calc'] —— 第二次调用串扰了第一次的数据!
print(append_tool.__defaults__) # (['search', 'calc'],) —— 函数元数据被直接污染!
防范准则:
函数形参的默认值严禁使用任何可变对象([], {} 等)。标准做法是使用不可变的单例 None 作为默认占位符,并在函数体内安全初始化:
def append_tool_safe(tool_name: str, tools: list[str] | None = None) –> list[str]:
if tools is None:
tools = []
tools.append(tool_name)
return tools
2.5 字符串处理:f-string
name, n = "agent", 3
f"hello {name}" # ↔ String.format("hello %s", name)
f"{n} items" # 自动调用 str()
f"{n:03d}" # 格式化规格 ↔ %03d / String.format
f"{price:.2f}" # 保留两位小数
f"{value=}" # 调试输出:value=42
f"{json.dumps(obj)}" # {} 内可以放任意表达式
常用方法对照:
| s.length() | len(s) |
| s.contains("x") | "x" in s |
| s.startsWith("x") | s.startswith("x") |
| s.toUpperCase() | s.upper() |
| s.trim() | s.strip() |
| s.split(",") | s.split(",") |
| String.join(",", list) | ",".join(list) |
| s.replace("a","b") | s.replace("a", "b") |
| String.valueOf(i) | str(i)(转 int 是 int(s)) |
| s.isEmpty() | not s |
多行字符串用三引号(常用于书写 Prompt):
system_prompt = """You are a helpful assistant.
Answer in JSON format.
Rules:
1. Be concise.
2. Cite sources."""
2.6 推导式 ↔ Java Stream API
推导式是 Python 对 Stream 大部分场景的替代,更简洁且是同步求值:
nums = [1, 2, 3, 4, 5, 6]
# map
squares = [n * n for n in nums] # nums.stream().map(n -> n*n).toList()
# filter
evens = [n for n in nums if n % 2 == 0] # .filter(n -> n%2==0)
# map + filter
big = [n * 10 for n in nums if n > 3] # .filter().map()
# flatMap
words = [w for line in lines for w in line.split()]
# reduce
total = sum(nums) # .reduce(0, Integer::sum)
mx = max(users, key=lambda u: u["age"]) # .max(comparing(…))
any_ = any(n > 5 for n in nums) # .anyMatch(…)
all_ = all(n > 0 for n in nums) # .allMatch(…)
count = sum(1 for n in nums if n > 3) # .filter().count()
字典/集合推导式:
users = [{"id": 1, "name": "tom"}, {"id": 2, "name": "jerry"}]
by_id = {u["id"]: u for u in users} # ↔ Collectors.toMap
names = {u["name"] for u in users} # set 推导式
何时仍用 Stream 风格:Python 的 itertools / map / filter 存在,但团队规范普遍首选推导式;超长链式惰性处理才考虑生成器(第 4 章)。



