跳至主要内容

Python学习指南:从Go和PHP开发者的视角

Python 从入门到精通:写给 Go / PHP 开发者

本文假设你已经熟练掌握 Go 和/或 PHP,重点不是从零讲解"什么是变量""什么是函数",而是用你已有的知识做类比,把精力集中在 Python 与 Go/PHP 真正不同的地方——尤其是类型系统、并发模型、包生态和"Pythonic"的编程习惯。


目录

  1. 核心定位差异:三门语言分别在解决什么问题
  2. 环境搭建与工具链
  3. 基础语法速成
  4. 类型系统:动态但可以强类型提示
  5. 核心数据结构
  6. 函数:从简单到高级
  7. 面向对象编程的 "Python 方式"
  8. 错误处理机制
  9. 迭代器、生成器与惰性求值
  10. 装饰器与上下文管理器
  11. 并发与并行模型(重点对比 Goroutine)
  12. 包管理与生态系统
  13. 代码风格与工程规范
  14. 测试
  15. 常用框架速览
  16. 性能优化与部署注意事项
  17. 进阶学习路线图

1. 核心定位差异:三门语言分别在解决什么问题

维度 Go PHP Python
设计哲学 简单、显式、编译期安全 Web 请求-响应模型,"能跑就行" 可读性优先,"应该只有一种明显的做法"
类型系统 静态强类型,编译期检查 动态弱类型(PHP 8 起类型提示增强) 动态强类型,可选静态类型提示(type hints)
执行方式 编译为原生二进制 解释执行(通常配合 PHP-FPM) 解释执行(CPython 字节码 + 虚拟机)
并发模型 Goroutine + Channel,语言原生 天然无状态、多进程模型,协程需额外扩展 GIL 限制下的多线程 + asyncio 协程
典型场景 后端服务、基础设施、CLI 工具 Web 应用(尤其传统 LAMP 架构) 数据科学、AI/ML、脚本自动化、Web 后端
哲学关键词 gofmt,一切从简 实用主义,历史包袱重 import this(Python 之禅),"电池自带"

给你的心智模型:如果说 Go 是"给你一套工具,逼你按规矩来",PHP 是"给你自由,出了问题算你倒霉",那 Python 就是"给你自由,但社区会用约定俗成的方式暗示你正确的做法"——这也是为什么 Python 有大量"Pythonic"这种说法,而 Go/PHP 社区较少这么强调风格。


2. 环境搭建与工具链

2.1 版本管理

Go 用 go.mod 里的 go 1.22 声明版本,配合系统安装的 Go 版本;PHP 通常靠系统包管理器或 phpenv。Python 的痛点历史上是版本混乱(2 vs 3,系统 Python vs 项目 Python),现代做法:

# 类似 Go 的多版本管理,推荐用 uv 或 pyenv
curl -LsSf https://astral.sh/uv/install.sh | sh
uv python install 3.12
uv python pin 3.12   # 类似 go.mod 里指定 go version

2.2 虚拟环境:Python 特有的概念

这是 Go/PHP 开发者最容易踩坑的地方。Go 的依赖天然隔离在 go.mod + GOPATH/pkg/mod;PHP 的 composer.json 依赖装在项目的 vendor/ 目录里,天然隔离。

Python 不同:默认情况下 pip install 会装到全局或用户目录,多个项目会互相污染依赖版本。所以 Python 需要显式创建"虚拟环境":

python -m venv .venv        # 创建虚拟环境(类似隔离出一个干净的 vendor/)
source .venv/bin/activate   # 激活(Windows: .venv\Scripts\activate)
pip install requests        # 现在装的包只在这个 venv 里

现代工具 uv / poetry 会自动帮你管理虚拟环境,体验更接近 composer installgo mod tidy

uv init myproject && cd myproject
uv add requests             # 类似 composer require / go get
uv run python main.py       # 自动使用项目虚拟环境运行

关键心智转换:Go 的依赖隔离是"语言级别原生的",PHP 是"约定目录结构",Python 的隔离是"需要你主动创建并激活一个环境"——忘记激活虚拟环境是新手最常见的坑。


3. 基础语法速成

3.1 用缩进代替大括号

# Python:缩进即语法,冒号开启代码块
def greet(name):
    if name:
        print(f"Hello, {name}")
    else:
        print("Hello, stranger")
// Go:大括号显式界定作用域
func greet(name string) {
    if name != "" {
        fmt.Printf("Hello, %s\n", name)
    } else {
        fmt.Println("Hello, stranger")
    }
}
  • 没有 {},没有分号,缩进错误直接是语法错误IndentationError),比 gofmt 更强制。
  • 一般用 4 个空格缩进(PEP 8 规范),不要混用 tab 和空格。

3.2 没有 $ 前缀,没有声明关键字

PHP 变量必须带 $;Go 用 var:=;Python 直接赋值即声明:

name = "Alice"      # 不需要 var / let / $
age = 30
is_admin = False    # 布尔值首字母大写:True / False(不是 true/false)
nothing = None       # 对应 Go 的 nil,PHP 的 null

3.3 字符串格式化:f-string 是首选

name, age = "Alice", 30
print(f"{name} is {age} years old")   # f-string,类似 PHP 的 "$name is {$age}"

对比: - Go: fmt.Sprintf("%s is %d years old", name, age) - PHP: "{$name} is {$age} years old"sprintf(...)

3.4 注释与文档字符串

# 单行注释
"""
多行字符串常被用作模块/函数的文档字符串(docstring)
"""

def add(a, b):
    """返回 a 与 b 的和。"""   # 紧跟函数定义的字符串会被工具识别为文档
    return a + b

这点与 Go 的 // Add returns...(必须写在函数正上方、以函数名开头)以及 PHP 的 PHPDoc /** @param */ 都不同——Python 的 docstring 是运行时可访问的字符串对象add.__doc__),而不仅仅是注释。


4. 类型系统:动态但可以强类型提示

这是三门语言差异最大、也最容易让 Go 开发者不适应的部分。

4.1 核心区别

Go PHP Python
类型检查时机 编译期 运行时(PHP 8 类型提示部分编译期) 运行时(type hints 需要额外工具检查)
变量类型 声明后不可变 可随时改变类型 可随时改变类型
类型提示是否强制 强制 可选(declare(strict_types=1) 可强化) 完全可选,仅供人类和工具参考
x = 5        # x 现在是 int
x = "hello"  # 完全合法!x 现在是 str,这在 Go 里是编译错误

4.2 Type Hints:Python 给自己"打的补丁"

Python 社区意识到大型项目缺乏类型检查很痛苦,于是引入了类型注解(不改变运行时行为,只是给 IDE 和 mypy/pyright 这类工具看):

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

from typing import Optional, Union

def find_user(user_id: int) -> Optional[dict]:   # 类似 Go 的 (*User, error) 但语义是"可能是 None"
    ...

# Python 3.10+ 可以用 | 代替 Union
def parse(value: int | str) -> str:
    ...

关键点:这些类型提示默认不会在运行时强制检查

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

add("1", "2")  # 不会报错,会返回 "12"(字符串拼接),除非你用 mypy 静态检查

这与 Go 编译器的强制类型检查完全不同——Python 的类型系统更像是"文档 + 可选的 CI 检查工具",而非语言强制。生产项目通常会配置 mypypyright 在 CI 中跑类型检查,相当于自己搭一个"半编译期检查"。

4.3 鸭子类型(Duck Typing)

这是 Python(和某种程度上 Go 的 interface)共享,但 PHP 较少强调的理念:

def make_it_quack(duck):
    duck.quack()   # 不关心 duck 的具体类型,只要它有 quack() 方法就行

class Duck:
    def quack(self): print("Quack!")

class Person:
    def quack(self): print("I'm quacking like a duck")

make_it_quack(Duck())    # 都能运行
make_it_quack(Person())

对比 Go 的 interface(显式定义方法集合,编译期检查):Python 没有"实现接口"的概念,只要方法签名对上就能用,运行时才会报错(如果对不上)。这比 Go 更灵活,但也更容易在运行时才发现问题——所以大型 Python 项目非常依赖类型提示 + 单元测试来弥补这个"安全网"的缺失。


5. 核心数据结构

5.1 对照表

Python Go PHP 说明
list slice 数组(列表模式) 有序、可变、可混合类型
tuple 固定长度 array 有序、不可变
dict map 数组(关联模式) 键值对,Python 3.7+ 保证插入顺序
set 无原生,需自己实现 无序不重复集合

5.2 list:比 Go slice 更灵活,但没有类型约束

nums = [1, 2, 3]
nums.append(4)             # 类似 Go 的 append(nums, 4),但是原地修改
mixed = [1, "two", 3.0]    # 合法!list 不要求同一类型(Go slice 做不到)

# 列表推导式(list comprehension)——Python 最标志性的语法糖之一
squares = [x**2 for x in range(10)]              # 等价于 for 循环 + append
evens = [x for x in range(20) if x % 2 == 0]      # 带过滤条件

对比 Go:

nums := []int{1, 2, 3}
nums = append(nums, 4)

对比 PHP:

$nums = [1, 2, 3];
$nums[] = 4;

5.3 dict:Python 里"万物皆可用字典表达"

user = {"name": "Alice", "age": 30}
user["email"] = "a@example.com"

# 字典推导式
squares_map = {x: x**2 for x in range(5)}

# get 方法避免 KeyError(类似 Go 的 v, ok := m[key])
age = user.get("age", 0)   # 找不到 key 时返回默认值 0,而不是报错

对比 Go 的 map 访问是"两个返回值"模式(v, ok := m[key]),Python 用 .get() 提供默认值,或者 try/except KeyError 捕获异常。

5.4 tuple:不可变序列,常被 Go 开发者当成"多返回值"

def divide(a, b):
    return a // b, a % b   # 返回一个 tuple

quotient, remainder = divide(10, 3)   # 自动解包,感觉很像 Go 的多返回值

注意区别:Go 的多返回值是语言层面的特性(函数签名里声明),Python 本质上是"返回了一个 tuple,然后解包赋值"——语法糖,但底层机制不同。

5.5 set:去重与集合运算

a = {1, 2, 3}
b = {2, 3, 4}
print(a & b)   # 交集 {2, 3}
print(a | b)   # 并集 {1, 2, 3, 4}
print(a - b)   # 差集 {1}

Go 和 PHP 都没有原生 set 类型,通常用 map[T]struct{} 或关联数组模拟,Python 原生支持且带运算符重载,非常好用。


6. 函数:从简单到高级

6.1 默认参数、关键字参数

def greet(name, greeting="Hello", punctuation="!"):
    return f"{greeting}, {name}{punctuation}"

greet("Bob")                          # 用默认值
greet("Bob", greeting="Hi")           # 关键字传参,顺序可以打乱
greet(name="Bob", punctuation="?")

PHP 8 也支持命名参数(greet(name: "Bob")),但 Go 完全不支持默认参数和命名参数——这是 Go 开发者转 Python 会觉得"解放"的地方,也是容易滥用导致函数签名混乱的地方。

6.2 可变参数:*args**kwargs

def sum_all(*args):              # 类似 Go 的变长参数 func sum(nums ...int)
    return sum(args)

def make_config(**kwargs):       # 收集所有关键字参数为 dict,PHP/Go 都没有直接对应物
    return kwargs

sum_all(1, 2, 3, 4)
make_config(host="localhost", port=8080)  # → {"host": "localhost", "port": 8080}

6.3 一等公民函数与 lambda

add = lambda a, b: a + b   # 匿名函数,类似 Go 的 func(a, b int) int { return a + b }

funcs = [str.upper, str.lower, str.title]
for f in funcs:
    print(f("hello"))

Python 的 lambda 只能是单个表达式(不能有多条语句),比 Go 的匿名函数、PHP 的箭头函数 fn($x) => $x + 1 更受限,复杂逻辑必须用 def 定义普通函数。

6.4 闭包

def make_counter():
    count = 0
    def increment():
        nonlocal count   # 类似 Go 闭包捕获外部变量,但需要显式声明 nonlocal 才能修改
        count += 1
        return count
    return increment

counter = make_counter()
print(counter())  # 1
print(counter())  # 2

易错点:Python 闭包默认只能"读"外部变量,要"写"必须加 nonlocal(或对模块级变量用 global),Go 闭包直接捕获引用可读可写,没有这个限制。


7. 面向对象编程的 "Python 方式"

7.1 类的基本写法

class User:
    def __init__(self, name, age):   # 构造函数,self 显式作为第一个参数(类似 Go 方法的 receiver)
        self.name = name
        self.age = age

    def greet(self):
        return f"Hi, I'm {self.name}"

    def __repr__(self):              # 魔术方法,控制 print(user) 的输出,类似 PHP 的 __toString
        return f"User(name={self.name!r}, age={self.age})"

u = User("Alice", 30)
print(u.greet())
print(u)   # 自动调用 __repr__

关键区别: - self 必须显式声明为第一个参数,Python 不会隐式注入(PHP 的 $this、Go 的 receiver (u *User) 都是隐式/语法层面处理)。 - Python 用双下划线包裹的"魔术方法"(__init__, __repr__, __eq__, __len__...)来实现运算符重载和协议,这一套体系比 PHP 的魔术方法(__construct, __toString, __get)更庞大,也比 Go(完全没有运算符重载)灵活得多。

7.2 继承与多重继承

class Animal:
    def speak(self):
        raise NotImplementedError

class Swimmer:
    def swim(self):
        print("Swimming...")

class Duck(Animal, Swimmer):      # 多重继承!Go 没有类继承概念,PHP 也不支持多继承
    def speak(self):
        print("Quack!")

d = Duck()
d.speak()
d.swim()

Go 开发者要注意:Go 用 struct 组合(embedding)模拟"类似继承"的效果,是显式的组合;Python 是真正的类继承,且支持多重继承(通过 C3 线性化算法解决菱形继承问题,即 MRO——Method Resolution Order)。PHP 只支持单继承 + interface + trait。

7.3 属性与封装:约定优于强制

class User:
    def __init__(self, name):
        self._name = name       # 单下划线:约定"受保护",仍可访问,纯靠自觉
        self.__secret = "abc"   # 双下划线:触发 name mangling,变成 _User__secret,较难(非不能)访问

    @property
    def name(self):             # 用装饰器实现类似 PHP getter 的效果
        return self._name

    @name.setter
    def name(self, value):
        if not value:
            raise ValueError("name 不能为空")
        self._name = value

Go 用大小写首字母控制导出(Name 导出,name 不导出,包级别强制);PHP 有 private/protected/public 关键字,编译期(其实是运行时但语言强制)检查;Python 没有真正的私有属性,一切都是约定——这是很多 Go 开发者最初最不适应的地方。

7.4 Dataclass:给"贫血模型"减负

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2)   # True,自动生成 __eq__、__repr__、__init__

类似 Go 的 struct(字段声明 + 自动生成的零值构造),但 Python 需要显式用 @dataclass 装饰器"手动开启"这种行为,默认的 class 不会自动生成这些方法。


8. 错误处理机制

这是三门语言哲学分歧最大的地方之一。

8.1 Go:显式返回 error,调用方必须显式检查

result, err := divide(10, 0)
if err != nil {
    log.Fatal(err)
}

8.2 PHP:异常为主,配合部分返回值风格

try {
    $result = divide(10, 0);
} catch (DivisionByZeroError $e) {
    echo $e->getMessage();
}

8.3 Python:异常是唯一的错误处理机制

def divide(a, b):
    if b == 0:
        raise ValueError("除数不能为零")   # 抛出异常,类似 PHP 的 throw
    return a / b

try:
    result = divide(10, 0)
except ValueError as e:
    print(f"出错了: {e}")
except (TypeError, ZeroDivisionError) as e:   # 可以同时捕获多种异常类型
    print(f"类型或除零错误: {e}")
else:
    print("成功,没有异常")     # try 块没有异常时执行
finally:
    print("无论如何都会执行")   # 类似 Go 的 defer,但语义是"清理"而非"延迟调用"

给 Go 开发者的关键提醒:Python 没有类似 Go if err != nil 的惯用法,也没有让你"选择忽略错误"的空间——异常会自动向上传播,不捕获就会导致程序崩溃并打印堆栈跟踪(traceback)。这意味着:

  • 不能假设函数调用"大概率不会出错"就不处理,因为异常会自动冒泡直到被捕获或使程序退出。
  • 但你也容易忘记某个函数可能抛异常,因为函数签名不像 Go 那样强制暴露"这里可能出错"(Python 类型提示里没有强制的"checked exception"机制,Java 有但 Python 没有)。

8.4 自定义异常

class InsufficientFundsError(Exception):
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(f"余额 {balance} 不足以支付 {amount}")

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(balance, amount)
    return balance - amount

8.5 with 语句:自动资源清理

with open("data.txt") as f:    # 类似 Go 的 defer file.Close(),但更结构化
    content = f.read()
# 离开 with 代码块后,文件自动关闭,即使中途抛出异常

9. 迭代器、生成器与惰性求值

这一块 Go 和 PHP 都没有直接对应的成熟语言特性(Go 1.23 引入了 range-over-func,PHP 有 Generator 但用得少),是 Python 非常标志性的部分。

9.1 生成器函数:yield

def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield a          # 每次调用暂停在这里,返回一个值,下次从这里继续
        a, b = b, a + b

for num in fibonacci(10):
    print(num)

yield 让函数变成一个惰性求值的迭代器——不会一次性生成整个列表占用内存,这对处理大数据流(比如逐行读取超大文件)非常关键:

def read_large_file(path):
    with open(path) as f:
        for line in f:
            yield line.strip()   # 一次只读一行到内存,而不是 read().splitlines() 整个加载

9.2 生成器表达式:比 list comprehension 更省内存

squares_list = [x**2 for x in range(1000000)]     # 立即生成 100 万个元素,占用大量内存
squares_gen = (x**2 for x in range(1000000))       # 惰性生成,用多少算多少

10. 装饰器与上下文管理器

10.1 装饰器:Python 版的"中间件"

import time
import functools

def timer(func):
    @functools.wraps(func)      # 保留原函数的元信息(名字、docstring)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} 耗时 {time.time() - start:.4f}s")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)

slow_function()   # 自动被计时

概念上类似 Go 的中间件模式(func(http.Handler) http.Handler)或 PHP 框架里的中间件,但 Python 把它做成了语言级别的语法糖(@decorator),可以直接套用在任何函数/方法/类上,用途极广(缓存、权限校验、重试、日志、Flask/FastAPI 的路由注册等)。

10.2 上下文管理器:with 背后的协议

class DatabaseConnection:
    def __enter__(self):
        print("打开连接")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("关闭连接")
        return False   # 返回 False 表示不吞掉异常

with DatabaseConnection() as conn:
    print("使用连接中...")

这套 __enter__/__exit__ 协议是 Python 处理"资源获取即初始化"的标准方式,比 Go 的 defer 更结构化(明确区分进入/退出逻辑),也比 PHP 没有原生对应物要更完善。


11. 并发与并行模型(重点对比 Goroutine)

这是 Go 开发者最需要重新建立心智模型的部分。

11.1 GIL:Python 并发的最大特殊性

CPython(最主流的 Python 实现)有一个全局解释器锁(GIL),同一时刻只有一个线程在执行 Python 字节码。这意味着:

  • 多线程无法利用多核做 CPU 密集型并行计算(这点与 Go 的 goroutine 天差地别,Go 天生可以用多核跑 CPU 密集任务)。
  • 多线程仍然对 I/O 密集型任务有效(网络请求、文件读写时会释放 GIL)。

注:Python 3.13 开始有实验性的"free-threaded"(无 GIL)构建版本,但截至目前主流生产环境仍以有 GIL 的标准解释器为主,具体现状建议关注 Python 官方发布信息。

11.2 三种并发方式对比

# 方式一:多线程(threading)—— 适合 I/O 密集型,类似传统多线程编程
import threading

def worker(n):
    print(f"Worker {n}")

threads = [threading.Thread(target=worker, args=(i,)) for i in range(5)]
for t in threads: t.start()
for t in threads: t.join()

# 方式二:多进程(multiprocessing)—— 绕开 GIL,适合 CPU 密集型,代价是进程间通信开销
from multiprocessing import Pool

def square(x):
    return x * x

with Pool(4) as p:
    results = p.map(square, range(10))

# 方式三:asyncio —— 单线程协程,适合高并发 I/O,语法上最接近 Go 的并发风格
import asyncio

async def fetch_data(n):
    await asyncio.sleep(1)     # 模拟 I/O 等待,期间可以执行其他协程
    return f"data {n}"

async def main():
    results = await asyncio.gather(*[fetch_data(i) for i in range(5)])
    print(results)

asyncio.run(main())

11.3 与 Goroutine 的关键差异

Go Goroutine Python asyncio
调度 语言运行时自动抢占式调度,多核并行 单线程协作式调度,await 主动让出控制权
语法侵入性 几乎无侵入,go func(){}() 即可 侵入性强:async def/await 会"传染"整条调用链——异步函数只能被异步函数或事件循环调用
通信 Channel(chan),CSP 模型 asyncio.Queue,或直接 await 返回值
CPU 密集任务 天然支持多核并行 必须用 multiprocessing 绕开 GIL

给 Go 开发者的直觉提醒:不要指望 asynciothreading 能像 goroutine 一样"无脑并行提速"。如果任务是 CPU 密集型(如图像处理、数值计算),Python 的默认选择应该是 multiprocessing,或者干脆用 C 扩展/Numpy 这类底层绕开 GIL 的库。


12. 包管理与生态系统

12.1 对照表

Go PHP Python
包索引 无中心索引,直接 Git 仓库拉取 Packagist PyPI(Python Package Index)
依赖声明文件 go.mod composer.json pyproject.toml(现代标准)/ requirements.txt(传统)
锁定文件 go.sum composer.lock uv.lock / poetry.lock
安装命令 go get composer require uv add / pip install
命名空间隔离 Module path 天然隔离 PSR-4 autoload 命名空间 模块/包路径,无强制命名空间隔离,容易和标准库重名

12.2 现代 Python 依赖管理(推荐用 uv)

uv init my-project
cd my-project
uv add fastapi uvicorn      # 添加依赖,类似 composer require / go get
uv add --dev pytest ruff    # 添加开发依赖,类似 composer require --dev
uv sync                     # 类似 composer install,按锁文件精确安装
uv run pytest               # 在项目虚拟环境里执行命令

pyproject.toml 类似 composer.json / go.mod 的角色:

[project]
name = "my-project"
version = "0.1.0"
dependencies = [
    "fastapi>=0.110",
    "uvicorn>=0.29",
]

12.3 历史包袱提醒

Python 的包管理工具历史上比较混乱(pip + virtualenv + setuptools + requirements.txt 是长期的"事实标准",但体验碎片化),近几年 poetry 和更新的 uv(Rust 编写,速度极快)逐渐统一了体验,新项目建议直接用 uv,可以获得接近 go mod / composer 的一致体验。老项目/教程里看到 pip install -r requirements.txt 也完全正常,是历史遗留但仍广泛使用的方式。


13. 代码风格与工程规范

Go PHP Python
官方格式化工具 gofmt(强制,几乎无争议) 无官方统一标准,社区用 PSR-12 无强制,社区事实标准是 PEP 8
主流格式化器 gofmt/goimports(内置) PHP-CS-Fixer black("不容置疑"的格式化器)、ruff format
静态检查 go vet, golangci-lint PHPStan, Psalm ruff(新一代,速度极快,集成 lint+format+import 排序)
命名规范 驼峰(导出首字母大写) 驼峰(类)/ 下划线或驼峰(方法,视框架而定) 蛇形命名(snake_case):变量/函数用 snake_case,类用 PascalCase,常量用 UPPER_SNAKE_CASE
# Python 命名规范示例
MAX_RETRIES = 3          # 常量

class HttpClient:        # 类名:PascalCase
    def send_request(self, url):    # 方法/函数名:snake_case
        pass

user_count = 10           # 变量:snake_case

PEP 8 是 Python 的"事实法律",地位类似 Go 的 gofmt 输出格式,但不是编译器强制,而是靠社区工具(ruff/black)和 code review 约定强制执行。现代项目几乎都会在 CI 里跑 ruff check + ruff format --check,效果上等同于 gofmt -l 检查。


14. 测试

# 用 pytest(事实标准,比标准库自带的 unittest 更简洁)
def add(a, b):
    return a + b

def test_add():                       # 函数名以 test_ 开头会被自动发现,无需注册
    assert add(2, 3) == 5             # 直接用 assert,pytest 会给出详细的失败信息

import pytest

@pytest.mark.parametrize("a,b,expected", [
    (1, 1, 2),
    (2, 3, 5),
    (-1, 1, 0),
])
def test_add_parametrized(a, b, expected):   # 参数化测试,类似表驱动测试(table-driven tests)
    assert add(a, b) == expected
uv run pytest -v          # 运行测试
uv run pytest --cov=.     # 加上覆盖率(需要 pytest-cov)

对比: - Go:go test ./...,表驱动测试是社区惯例但需要手写 []struct{} 切片。 - PHP:PHPUnit,@dataProvider 注解实现参数化测试。 - Python:pytestassert 直接用原生语法,@pytest.mark.parametrize 更简洁,且不需要继承任何基类(unittest 需要继承 TestCase,pytest 不需要)。


15. 常用框架速览

场景 Go PHP Python
Web 框架(重量级) Laravel, Symfony Django(自带 ORM、Admin、认证,"全家桶")
Web 框架(轻量级) Gin, Echo, Fiber Slim, Lumen Flask, FastAPI
现代异步 Web FastAPI(基于类型提示自动生成 OpenAPI 文档,原生 async,是目前 Python API 开发的主流选择)
ORM GORM Eloquent (Laravel) SQLAlchemy, Django ORM
任务队列 Laravel Queue Celery, RQ, Dramatiq
数据科学/AI(Python 独有优势) NumPy, Pandas, PyTorch, scikit-learn —— 这是 Python 相较于 Go/PHP 最大的生态优势

FastAPI 示例(体会类型提示如何驱动框架能力,这是 Python 特有的开发体验):

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class User(BaseModel):     # 用类型提示定义数据模型,自动做校验(类似 Go 的 struct tag,但更强大)
    name: str
    age: int

@app.post("/users")
async def create_user(user: User):    # 请求体自动解析+校验成 User 对象,类型错误自动返回 422
    return {"message": f"创建用户 {user.name}"}

16. 性能优化与部署注意事项

16.1 分发方式的根本差异

  • Go:编译为单一静态二进制文件,部署=复制一个文件,没有运行时依赖。
  • PHP:通常依赖服务器环境已装好 PHP 运行时 + PHP-FPM + Nginx/Apache。
  • Python:解释执行,部署时需要目标机器有匹配版本的 Python 解释器 + 所有依赖包,历史上是"依赖地狱"重灾区。现代做法通常是:
    • 用 Docker 打包整个环境(最常见)。
    • pyinstaller / nuitka 打包成单文件可执行程序(体验上更接近 Go,但文件体积大很多)。

16.2 性能心智调整

  • Python 是解释执行,纯计算性能通常比 Go 慢一个数量级以上,不适合写 CPU 密集的底层服务(这也是为什么高性能场景 Python 生态大量依赖 C/C++/Rust 扩展,比如 NumPy 底层是 C,Pydantic v2 核心是 Rust)。
  • 常见优化路径:先用纯 Python 写对逻辑 → 用 cProfile 找到瓶颈 → 瓶颈函数用 NumPy 向量化或改写为 C 扩展 / 用 Cython / 考虑用 Rust(PyO3)重写热点路径。
  • 不要一开始就微观优化,Python 的哲学是"先写对、写清楚,性能问题再针对性解决"。

17. 进阶学习路线图

第一阶段(1-2 周):语法与心智转换 - 完成本文第 3-8 节的所有代码示例,动手改写你熟悉的一个 Go/PHP 小项目为 Python 版本。 - 重点体会:没有分号/大括号、动态类型、异常处理取代显式 error 返回。

第二阶段(2-4 周):Pythonic 习惯用法 - 列表推导式、生成器、装饰器、上下文管理器(本文第 9-10 节)要练到"看到场景就会用"。 - 阅读优秀开源项目源码(推荐 httpxclickrequests),体会地道的 Python 风格。 - 学习并实践 ruff/black + mypy/pyright,把类型提示和静态检查加入日常工作流。

第三阶段(1-2 月):生态与工程化 - 精通 uv(或 poetry)的依赖管理,理解 pyproject.toml 全部字段。 - 用 pytest 给项目补齐单元测试和参数化测试。 - 根据方向选择框架深入:Web 开发选 FastAPI(异步优先)或 Django(全家桶),数据方向选 Pandas + NumPy。

第四阶段(持续):并发与性能 - 彻底搞懂 GIL 的影响范围,能准确判断一个任务该用 asynciothreading 还是 multiprocessing。 - 学会用 cProfile/py-spy 做性能剖析,知道什么时候该用 Numpy 向量化或 C 扩展。

推荐资源 - 官方文档:docs.python.org(Python 官方文档质量很高,优先信任)。 - 《Python 之禅》:在解释器里输入 import this,是理解 Python 设计哲学最快的方式。 - PEP 8(代码风格)、PEP 484(类型提示)官方规范原文,遇到风格争议时的权威参考。


附:一张图记住核心差异

Go       —— 编译期安全 + 显式 error + Goroutine 并行  → "把复杂度留给写代码的人"
PHP      —— 请求-响应模型 + 灵活但松散                  → "能跑就行,历史包袱重"
Python   —— 运行时灵活 + 异常处理 + GIL限制的并发       → "可读性优先,工具链弥补语言的宽松"

理解这三种哲学的根本差异后,你会发现 Python 的很多"奇怪"设计(没有私有属性、GIL、鸭子类型、强制缩进)背后都有一致的价值观:优先保证代码好读、写起来自由,把安全性的部分交给工具链(mypy、ruff、pytest)而不是语言本身强制。这与 Go "语言层面强制简单和安全"的哲学形成有趣的对照。

此博客中的热门博文

Elasticsearch 读写原理指南

### 1. 什么是 segment,里面装了什么? 在 Lucene(也是 Elasticsearch)里,索引被切分成若干 **segment(段)**,每个 segment 是一个完整的、只读的倒排索引单元。一个 segment 包含: * **倒排词典** —— 用 **FST(Finite‑State Transducer)** 以高度压缩的形式保存每个字段出现的所有 term 以及 term→ord 的映射。对应的磁盘文件是 `*.tim`(新版)或 `*.tis/*.tii`(旧版)。 * **倒排列表(postings)** —— 保存每个 term 出现的文档 ID、频次、位置信息等,文件名通常是 `*.doc`、`*.pos`、`*.pay`。 * **存储字段**(_source、store:true 的字段)—— 以二进制块的形式写入 `*.fdt` / `*.fdx`。 * **doc‑values、norms、向量** 等辅助结构,分别保存在 `*.dv`、`*.norm`、`*.tv` 等文件里。 * **deleted‑docs bitmap**(`*.del`),标记哪些文档已被删除或被更新。 所有这些文件在 segment **写入磁盘后即成为只读**,后续的查询只能读取,永远不会在原文件上进行增删改。 --- ### 2. 原始文档和 FST 为什么都在 segment 里? * **原始文档**:Elasticsearch 默认把完整的 JSON(_source)以及任何 `store:true` 的字段写入 segment 的 `*.fdt/*.fdx` 文件。每个 segment 保存自己的那部分文档,旧的 segment 在合并前仍然保留,直到合并后被删除。 * **FST**:每个字段的词典在每个 segment 中单独维护,采用 FST 进行前缀共享和字节压缩。这样即使同一个 term 在多个 segment 中出现,也会在每个 segment 里拥有独立的映射,查询时只需要在对应 segment 的 FST 中定位即可。 --- ### 3. 查询时到底是怎么遍历 segment 的? 1. **请求入口**      客户端的搜索请求先到达 **协调节点**,协调节点把请求 ...

LLM缓存详解

 可以把“大模型缓存”理解成: 把已经算过的结果(或中间结果)存下来,下次尽量复用 。但这里面其实分几层,不只是简单的“问题→答案”缓存。 1️⃣ 常见的几种缓存类型 (1)KV Cache(推理内部缓存) Transformer 在生成时,会把前面 token 的 Key/Value 向量 缓存下来。 本质:避免重复计算 attention 作用: 同一请求内部加速 特点: 👉 只对“同一上下文继续生成”有效 👉 不跨用户、不跨请求 这类缓存是你体感“流式输出越来越快”的原因之一。 (2)Prompt Cache(提示词缓存) 缓存的是: 相同(或高度相似)的 prompt → 对应的中间表示 / 输出 典型场景: 系统提示词(system prompt)很长 多轮对话里前文基本不变 👉 这里能省掉 前缀计算成本(prefill) (3)Embedding / 语义缓存(Semantic Cache) 这个才是你问题的关键 👇 不是按“字符串完全一致”,而是: 把问题转成向量 → 找“语义相似”的历史问题 → 直接复用答案 2️⃣ 为什么命中缓存成本低很多? 因为大模型推理成本主要在两块: (1)Prefill(吃 prompt) 复杂度 ~ O(n²) 很贵(尤其长 prompt) (2)Decode(逐 token 生成) 每个 token 都要算一遍模型 而缓存命中后: KV cache:不用重复 attention Prompt cache:不用重新 encode 语义缓存: 直接跳过模型推理 👉 相当于从: 几十~几百毫秒 + GPU算力 变成: 一次向量检索(毫秒级)+ 直接返回 所以成本差一个数量级是正常的。 3️⃣ “每个人问法不同,怎么命中缓存?” 这是核心难点,也是工程重点👇 ❌ 不能靠字符串匹配 比如: “今天天气怎么样” “今天外面热不热” 字符串完全不同 → 必须 miss ✅ 用语义相似度(Embedding) 流程一般是: 把问题转 embedding(向量) 在向量数据库里找 TopK 相似问题 如果相似度 > 阈值(比如 0.9) 直接返回缓存答案 一个简单示意 Q1: 北京天气怎么样 → embedding A Q2: 北京今天热吗 → embedding B cosine(A, B) ≈ 0.95...

事务的ACID是什么

 事务的 ACID 是数据库事务必须满足的四个基本性质,用来保证在并发和故障情况下数据的正确性与可靠性: A(Atomicity,原子性) 一个事务中的操作要么 全部成功 ,要么 全部失败回滚 ,不存在“只做了一半”的中间状态。 C(Consistency,一致性) 事务执行前后,数据库都必须处于 一致的合法状态 ,满足约束(如主键、外键、唯一性、业务规则等)。 I(Isolation,隔离性) 并发执行的多个事务之间 相互隔离 ,一个事务未提交的中间结果对其他事务不可见(具体强弱由隔离级别决定)。 D(Durability,持久性) 一旦事务提交成功,其结果会被 永久保存 ,即使系统崩溃也不会丢失(通常依赖 WAL/redo log 等机制)。 一句话记忆: 要么全做完、前后不破坏规则、互不干扰、做完不丢。