如果你已经会 Java,学习 Python 时不要把它当成“没有分号的 Java”。Python 的核心体验更接近:
- 用更少代码表达数据处理、脚本自动化和 API 调用。
- 用缩进组织代码块,而不是
{}。 - 用动态类型运行代码,但用类型提示提升可读性和工具支持。
- 用
list、dict、dataclass、Pydantic model 承载大量数据结构。 - 用模块文件和包目录组织代码,而不是 Java 的 class-first 思维。
几个快速映射:
| Java | Python |
|---|---|
String |
str |
Integer / int |
int |
Double / BigDecimal |
float / decimal.Decimal |
Boolean |
bool |
null |
None |
List<T> |
list[T] |
Map<K, V> |
dict[K, V] |
Set<T> |
set[T] |
record / DTO |
dataclass / Pydantic model |
try/catch/finally |
try/except/finally |
static field |
类属性 |
| 实例字段 | 实例属性 |
后面的内容会以 Python 本身为主,不再频繁对比 Java。
Python 常见基本类型:
int:整数float:浮点数str:字符串bool:布尔值,只有True和FalseNone:表示没有值
age = 30
temperature = 0.7
name = "Python"
is_ready = True
result = None
print(type(age)) # <class 'int'>
print(type(temperature)) # <class 'float'>
print(type(name)) # <class 'str'>
print(type(is_ready)) # <class 'bool'>
print(result is None) # True注意:
- Python 的布尔值首字母大写:
True、False。 - 空值是
None,不是null。 - 判断是否为
None推荐用is None,不要用== None。
字符串常用写法:
language = "Python"
message = f"I am learning {language}"
print(message) # I am learning Python
print(language.lower()) # python
print(language.upper()) # PYTHON
print(language.startswith("Py")) # Truef-string 是 Python 最常用的字符串格式化方式。
# 使用 f-string 拼接变量
name = "张三"
age = 25
message = f"你好,我叫{name},今年{age}岁。"
print(message) # 输出: 你好,我叫张三,今年25岁。list 是有序、可变的列表。
tools = ["search", "calculator", "database"]
tools.append("python")
tools.remove("calculator")
print(tools[0]) # search
print(tools[-1]) # python
print(len(tools)) # 3遍历列表:
for tool in tools:
print(tool)tuple 是有序、不可变的序列,适合表达固定结构。
point = (10, 20)
x, y = point
print(x)
print(y)函数返回多个值时,本质上也经常使用 tuple。这里有个容易忽略的点:决定 tuple 的是逗号,不是括号——200, "OK" 本身就是一个 tuple,括号只是可选的:
def get_status():
return 200, "OK" # 等价于 return (200, "OK"):逗号把两个值打包成一个 tuple
code, text = get_status() # 再按位置解包:code=200, text="OK"这就是 Python 的"打包 / 解包"(packing / unpacking):
return 200, "OK"把多个值打包成一个 tuple,code, text = ...再解包回多个变量。Java 没有等价语法,最接近的做法是返回一个数组或一个小对象再逐个取值——所以 Python 这种"函数直接返回多个值"的写法,底层其实一直是 tuple。
dict 是键值对结构,在 Python 生态里非常常见。
config = {
"model": "gpt-example",
"temperature": 0.2,
"stream": False,
}
print(config["model"])
print(config.get("timeout", 30))
config["timeout"] = 60遍历字典:
for key, value in config.items():
print(key, value)set 是无序、不重复集合。
tags = {"python", "agent", "python"}
print(tags) # {'python', 'agent'}
print("agent" in tags) # True
tags.add("fastapi")常用于去重:
names = ["a", "b", "a", "c"]
unique_names = set(names)score = 85
if score >= 90:
level = "A"
elif score >= 60:
level = "B"
else:
level = "C"
print(level)Python 使用缩进表示代码块。
numbers = [1, 2, 3]
for number in numbers:
print(number)需要下标时用 enumerate:
for index, value in enumerate(numbers):
print(index, value)count = 3
while count > 0:
print(count)
count -= 1break 结束整个循环。
for number in [1, 2, 3, 4, 5]:
if number == 3:
break
print(number)continue 跳过当前这一轮,进入下一轮。
for number in [1, 2, 3, 4, 5]:
if number % 2 == 0:
continue
print(number)def add(a, b):
return a + b
result = add(1, 2)def greet(name, message="Hello"):
return f"{message}, {name}"
print(greet("Ada"))
print(greet("Ada", "Hi"))注意:不要把可变对象作为默认参数。
不推荐:
def add_item(item, items=[]):
items.append(item)
return items推荐:
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return itemsdef create_user(name, age, active=True):
return {"name": name, "age": age, "active": active}
user = create_user(name="Ada", age=30, active=False)关键字参数能让调用更清楚。
*args 接收多个位置参数。
def total(*numbers):
return sum(numbers)
print(total(1, 2, 3))在函数内部,numbers 是一个 tuple。
**kwargs 接收多个关键字参数。
def build_config(**kwargs):
return kwargs
config = build_config(model="gpt-example", temperature=0.2)在函数内部,kwargs 是一个 dict。
推导式是 Python 里非常常见的数据转换写法。
普通写法:
numbers = [1, 2, 3, 4]
result = []
for number in numbers:
result.append(number * 2)推导式写法:
numbers = [1, 2, 3, 4]
result = [number * 2 for number in numbers]带过滤条件:
even_numbers = [number for number in numbers if number % 2 == 0]names = ["Ada", "Linus", "Guido"]
name_lengths = {name: len(name) for name in names}
print(name_lengths)把推导式的 [] 换成 (),得到的不是 list 而是生成器:它惰性求值,一次只算一个、用完即弃,适合处理大文件或流式数据,不必把全部结果一次性装进内存。
squares = (n * n for n in range(1_000_000)) # 不会立刻算一百万个
first = next(squares) # 要一个才算一个用 yield 能写自己的生成器函数:
def read_lines(path):
with open(path, encoding="utf-8") as f:
for line in f:
yield line.rstrip("\n") # 逐行产出,不把整个文件读进内存对 Java 程序员:生成器 ≈ 惰性的
Iterator/Stream。Agent 示例里的流式响应(for chunk in resp.stream(): ...)就是这个模型——一段段产出、边到边处理。
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
print(divide(10, 2))
print(divide(10, 0))finally 无论是否发生异常都会执行。
try:
value = int("123")
except ValueError:
value = 0
finally:
print("done")class InvalidScoreError(Exception):
pass
def validate_score(score):
if score < 0 or score > 1:
raise InvalidScoreError("score must be between 0 and 1")
return score使用:
try:
validate_score(1.5)
except InvalidScoreError as error:
print(error)import json
data = {"name": "Python", "type": "language"}
with open("example.json", "w", encoding="utf-8") as file:
json.dump(data, file, ensure_ascii=False, indent=2)
with open("example.json", "r", encoding="utf-8") as file:
loaded = json.load(file)
print(loaded)with 会自动关闭文件。
pathlib.Path 是更现代的路径处理方式。
from pathlib import Path
path = Path("example.txt")
path.write_text("hello python", encoding="utf-8")
content = path.read_text(encoding="utf-8")
print(content)
print(path.exists())遍历目录:
for path in Path(".").rglob("*.py"):
print(path)import json
text = json.dumps({"ok": True})from pathlib import Path
path = Path("example.txt")假设有文件:
scripts/
math_utils.py
main.py
math_utils.py:
def add(a, b):
return a + bmain.py:
from math_utils import add
print(add(1, 2))文件即模块(module),目录即包(package)。一个带 __init__.py 的目录就是一个包,可以用点号分层导入:
agent_lab/
__init__.py
models.py
tools.py
from agent_lab.models import ChatMessage # 包.模块 import 名字
from agent_lab import tools # 导入整个模块__init__.py可以是空文件,它的存在就标志“这个目录是一个包”(≈ Java 的 package 声明 + 目录结构)。- 从项目根目录运行(
python -m agent_lab.main,或在根目录跑 pytest),导入路径才稳定——这点和 Java 的 classpath 根类似。 - 本仓库最终的综合项目就是这种
包/ + scripts/ + tests/结构。
class User:
def __init__(self, name):
self.name = name
def say_hello(self):
return f"Hello, {self.name}"
user = User("Ada")
print(user.name)
print(user.say_hello())self 表示当前实例。
class User:
role = "member"
def __init__(self, name):
self.name = name
user = User("Ada")
print(user.role)
print(User.role)class Person:
def __init__(self, name):
self.name = name
def describe(self):
return f"{self.name} is a person"
class Student(Person):
def __init__(self, name, language):
super().__init__(name)
self.language = language
def describe(self):
return f"{self.name} learns {self.language}"使用:
student = Student("Ada", "Python")
print(student.describe())类型提示不会让 Python 变成编译期强类型语言,但能显著提升可读性、IDE 提示和静态检查体验。
def add(a: int, b: int) -> int:
return a + bdef join_names(names: list[str]) -> str:
return ", ".join(names)from typing import Any
def read_config(config: dict[str, Any]) -> str:
return config.get("model", "unknown")Any 表示任意类型。配置、JSON 数据、LLM 输出里经常会见到。
from typing import Optional
def find_user(user_id: str) -> Optional[dict[str, str]]:
if user_id == "missing":
return None
return {"id": user_id, "name": "Ada"}Optional[T] 表示 T 或 None。
在 Python 3.10+ 里也常写成:
def find_score() -> float | None:
return NoneCallable 表示函数类型。
from typing import Callable
def apply(value: int, func: Callable[[int], int]) -> int:
return func(value)
result = apply(3, lambda x: x * 2)Callable[[int], int] 表示接收一个 int,返回一个 int 的函数。
dataclass 适合定义轻量数据对象。
from dataclasses import dataclass
@dataclass
class EvalItem:
question: str
expected_answer: str
score: float | None = None
item = EvalItem(
question="What is Python?",
expected_answer="A programming language.",
)
print(item.question)
print(item.score)转成 dict:
from dataclasses import asdict
data = asdict(item)什么时候用 dataclass:
- 简单内部数据结构
- 脚本中的中间数据
- 测试数据对象
- 不需要复杂校验的 DTO
什么时候不用 dataclass:
- API 入参校验
- LLM JSON 输出校验
- 外部不可信数据校验
这些场景更适合 Pydantic。
你已经用过 @dataclass;在后面的阶段还会反复见到 @app.post(...)(FastAPI)、@field_validator(Pydantic)、@tool(LangChain)。这个 @xxx 就是装饰器。
装饰器本质是"接收一个函数、返回一个(通常被增强过的)函数"的函数。@ 只是语法糖:
def log_calls(func):
def wrapper(*args, **kwargs):
print(f"calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_calls # 等价于 greet = log_calls(greet)
def greet(name):
return f"Hello, {name}"对 Java 程序员:它长得像注解(
@Override、@RestController),但机制不同——Java 注解是元数据、靠框架反射读取;Python 装饰器是当场真的把函数包了一层或替换掉。所以@app.post("/chat")不只是"标记",它真的把你的函数注册进了路由表并返回了一个新函数。
读 Agent 示例时,看到 @ 不用慌:先看它装饰的那个函数做什么,装饰器多半只是在外面加了注册、校验、缓存、重试之类的一层。
- 缩进是语法,不是风格。块的边界由缩进决定,没有
{};同一块缩进必须一致(统一 4 空格,别混 Tab)。 - 变量是"名字绑定",不是 Java 引用变量的等价物。
b = a后两个名字指向同一个对象;对可变对象(list/dict)的原地修改,两个名字都看得到。 - 可变默认参数是陷阱:
def f(x=[])的[]只在定义时创建一次、被所有调用共享。用x=None再在函数内赋默认值(见 4.2)。 is判身份,==判值。判None一律用is None。- 整除是
//,/永远返回 float:7 / 2 == 3.5、7 // 2 == 3。没有++/--,用+= 1。 - 真值判断很"宽":空
""、空[]、空{}、0、None都视为"假"。if items:就能判"非空",不用len(items) > 0。 - 没有受检异常(checked exception):任何函数都可能抛任何异常,签名上看不出来;靠文档和阅读,别指望编译器提醒。
- 类型提示不强制:
x: int传字符串运行时不会报错(除非你跑 mypy)。它是给人和 IDE 看的,不是 Java 那样的编译期保证。 - 别一上来套复杂 OO。很多场景一个函数 + dict/dataclass 就够了;Python 示例更偏函数式 + 数据流,继承层级通常很浅。
本阶段配套了一个可运行示例脚本:
.\.venv\Scripts\python.exe scripts\stage1_syntax_examples.py也可以运行测试:
.\.venv\Scripts\python.exe -m pytest tests\test_stage1_syntax_examples.py建议学习方式:
- 先读本教程。
- 再打开
scripts/stage1_syntax_examples.py对照运行。 - 修改其中的参数和数据。
- 运行测试,看自己是否改坏了行为。
学完这一阶段,你应该能够:
- 看懂 Python 基本变量、字符串、列表和字典操作。
- 能写简单函数,并理解默认参数、关键字参数、
*args、**kwargs。 - 能用
for、while、if、break、continue写基础流程。 - 能用 list/dict comprehension 做简单数据转换,并理解生成器(
yield)的惰性产出。 - 能读写 JSON 和文本文件。
- 能定义简单类、dataclass 和自定义异常。
- 能看懂
list[str]、dict[str, Any]、Optional、Callable这类类型提示。 - 能理解装饰器(
@xxx)的本质,并用 module / package(__init__.py)组织本地代码。 - 能阅读 LangChain、LangGraph、AutoGen、LlamaIndex 示例里的基础 Python 语法,并避开本章的常见坑。
对照 scripts/stage1_syntax_examples.py 运行后,自己动手写:
- 读取一个 JSON 文件,过滤出满足条件的数据,再写成新的 JSON 文件。
- 写一个函数,把一批对话消息转换成
[{"role": ..., "content": ...}]这种常见 message 格式。 - 用
pathlib扫描一个目录,统计.py/.md/.json各有多少个。 - 写一个
dataclass表示一次模型调用记录(输入、输出、耗时、是否成功),并用asdict转成 dict。 - 把任务 1 的核心过滤函数抽出来,用
pytest写一个测试。