Skip to content

Latest commit

 

History

History
789 lines (541 loc) · 17.2 KB

File metadata and controls

789 lines (541 loc) · 17.2 KB

第 1 阶段:Python 基础语法教程

0. 给 Java 程序员的开场对比

如果你已经会 Java,学习 Python 时不要把它当成“没有分号的 Java”。Python 的核心体验更接近:

  • 用更少代码表达数据处理、脚本自动化和 API 调用。
  • 用缩进组织代码块,而不是 {}
  • 用动态类型运行代码,但用类型提示提升可读性和工具支持。
  • listdictdataclass、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。

1. 基本类型

Python 常见基本类型:

  • int:整数
  • float:浮点数
  • str:字符串
  • bool:布尔值,只有 TrueFalse
  • None:表示没有值
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 的布尔值首字母大写:TrueFalse
  • 空值是 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"))  #  True

f-string 是 Python 最常用的字符串格式化方式。

# 使用 f-string 拼接变量
name = "张三"
age = 25
message = f"你好,我叫{name},今年{age}岁。"
print(message)  # 输出: 你好,我叫张三,今年25岁。

2. 容器类型

2.1 list

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)

2.2 tuple

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。

2.3 dict

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)

2.4 set

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)

3. 控制流

3.1 if

score = 85

if score >= 90:
    level = "A"
elif score >= 60:
    level = "B"
else:
    level = "C"

print(level)

Python 使用缩进表示代码块。

3.2 for

numbers = [1, 2, 3]

for number in numbers:
    print(number)

需要下标时用 enumerate

for index, value in enumerate(numbers):
    print(index, value)

3.3 while

count = 3

while count > 0:
    print(count)
    count -= 1

3.4 break 和 continue

break 结束整个循环。

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)

4. 函数

4.1 基础函数

def add(a, b):
    return a + b

result = add(1, 2)

4.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 items

4.3 关键字参数

def create_user(name, age, active=True):
    return {"name": name, "age": age, "active": active}

user = create_user(name="Ada", age=30, active=False)

关键字参数能让调用更清楚。

4.4 *args

*args 接收多个位置参数。

def total(*numbers):
    return sum(numbers)

print(total(1, 2, 3))

在函数内部,numbers 是一个 tuple。

4.5 **kwargs

**kwargs 接收多个关键字参数。

def build_config(**kwargs):
    return kwargs

config = build_config(model="gpt-example", temperature=0.2)

在函数内部,kwargs 是一个 dict。

5. 推导式

推导式是 Python 里非常常见的数据转换写法。

5.1 list comprehension

普通写法:

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]

5.2 dict comprehension

names = ["Ada", "Linus", "Guido"]

name_lengths = {name: len(name) for name in names}

print(name_lengths)

5.3 生成器(generator)

把推导式的 [] 换成 (),得到的不是 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(): ...)就是这个模型——一段段产出、边到边处理。

6. 异常处理

6.1 try / except

def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None

print(divide(10, 2))
print(divide(10, 0))

6.2 finally

finally 无论是否发生异常都会执行。

try:
    value = int("123")
except ValueError:
    value = 0
finally:
    print("done")

6.3 自定义异常

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)

7. 文件操作

7.1 open

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 会自动关闭文件。

7.2 pathlib.Path

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)

8. 模块导入

8.1 import

import json

text = json.dumps({"ok": True})

8.2 from ... import ...

from pathlib import Path

path = Path("example.txt")

8.3 本地模块导入

假设有文件:

scripts/
  math_utils.py
  main.py

math_utils.py

def add(a, b):
    return a + b

main.py

from math_utils import add

print(add(1, 2))

8.4 包(package)与 __init__.py

文件即模块(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/ 结构。

9. 类和对象

9.1 class、实例属性、方法

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 表示当前实例。

9.2 类属性

class User:
    role = "member"

    def __init__(self, name):
        self.name = name


user = User("Ada")
print(user.role)
print(User.role)

9.3 继承

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())

10. 类型提示

类型提示不会让 Python 变成编译期强类型语言,但能显著提升可读性、IDE 提示和静态检查体验。

10.1 基础类型提示

def add(a: int, b: int) -> int:
    return a + b

10.2 list[str]

def join_names(names: list[str]) -> str:
    return ", ".join(names)

10.3 dict[str, Any]

from typing import Any


def read_config(config: dict[str, Any]) -> str:
    return config.get("model", "unknown")

Any 表示任意类型。配置、JSON 数据、LLM 输出里经常会见到。

10.4 Optional

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] 表示 TNone

在 Python 3.10+ 里也常写成:

def find_score() -> float | None:
    return None

10.5 Callable

Callable 表示函数类型。

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 的函数。

11. dataclass

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。

12. 装饰器(decorator)

你已经用过 @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 示例时,看到 @ 不用慌:先看它装饰的那个函数做什么,装饰器多半只是在外面加了注册、校验、缓存、重试之类的一层。

13. 给 Java 程序员的避坑清单

  • 缩进是语法,不是风格。块的边界由缩进决定,没有 {};同一块缩进必须一致(统一 4 空格,别混 Tab)。
  • 变量是"名字绑定",不是 Java 引用变量的等价物b = a 后两个名字指向同一个对象;对可变对象(list/dict)的原地修改,两个名字都看得到。
  • 可变默认参数是陷阱def f(x=[])[] 只在定义时创建一次、被所有调用共享。用 x=None 再在函数内赋默认值(见 4.2)。
  • is 判身份,== 判值。判 None 一律用 is None
  • 整除是 /// 永远返回 float7 / 2 == 3.57 // 2 == 3。没有 ++ / --,用 += 1
  • 真值判断很"宽":空 ""、空 []、空 {}0None 都视为"假"。if items: 就能判"非空",不用 len(items) > 0
  • 没有受检异常(checked exception):任何函数都可能抛任何异常,签名上看不出来;靠文档和阅读,别指望编译器提醒。
  • 类型提示不强制x: int 传字符串运行时不会报错(除非你跑 mypy)。它是给人和 IDE 看的,不是 Java 那样的编译期保证。
  • 别一上来套复杂 OO。很多场景一个函数 + dict/dataclass 就够了;Python 示例更偏函数式 + 数据流,继承层级通常很浅。

14. 可运行验证

本阶段配套了一个可运行示例脚本:

.\.venv\Scripts\python.exe scripts\stage1_syntax_examples.py

也可以运行测试:

.\.venv\Scripts\python.exe -m pytest tests\test_stage1_syntax_examples.py

建议学习方式:

  1. 先读本教程。
  2. 再打开 scripts/stage1_syntax_examples.py 对照运行。
  3. 修改其中的参数和数据。
  4. 运行测试,看自己是否改坏了行为。

15. 本阶段掌握标准

学完这一阶段,你应该能够:

  • 看懂 Python 基本变量、字符串、列表和字典操作。
  • 能写简单函数,并理解默认参数、关键字参数、*args**kwargs
  • 能用 forwhileifbreakcontinue 写基础流程。
  • 能用 list/dict comprehension 做简单数据转换,并理解生成器(yield)的惰性产出。
  • 能读写 JSON 和文本文件。
  • 能定义简单类、dataclass 和自定义异常。
  • 能看懂 list[str]dict[str, Any]OptionalCallable 这类类型提示。
  • 能理解装饰器(@xxx)的本质,并用 module / package(__init__.py)组织本地代码。
  • 能阅读 LangChain、LangGraph、AutoGen、LlamaIndex 示例里的基础 Python 语法,并避开本章的常见坑。

16. 练习任务(自己动手)

对照 scripts/stage1_syntax_examples.py 运行后,自己动手写:

  1. 读取一个 JSON 文件,过滤出满足条件的数据,再写成新的 JSON 文件。
  2. 写一个函数,把一批对话消息转换成 [{"role": ..., "content": ...}] 这种常见 message 格式。
  3. pathlib 扫描一个目录,统计 .py / .md / .json 各有多少个。
  4. 写一个 dataclass 表示一次模型调用记录(输入、输出、耗时、是否成功),并用 asdict 转成 dict。
  5. 把任务 1 的核心过滤函数抽出来,用 pytest 写一个测试。