跳至主要内容

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 install 或 go 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 检查工具",而非语言强制。生产项目通常会配置 mypy 或 pyright 在 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 开发者的直觉提醒:不要指望 asyncio 或 threading 能像 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:pytest 的 assert 直接用原生语法,@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 节)要练到"看到场景就会用"。 - 阅读优秀开源项目源码(推荐 httpx、click、requests),体会地道的 Python 风格。 - 学习并实践 ruff/black + mypy/pyright,把类型提示和静态检查加入日常工作流。

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

第四阶段(持续):并发与性能 - 彻底搞懂 GIL 的影响范围,能准确判断一个任务该用 asyncio、threading 还是 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 "语言层面强制简单和安全"的哲学形成有趣的对照。

此博客中的热门博文

PyTorch学习路线图

  第一阶段:基础知识(2-3周) 第1周:Python与机器学习基础 Python数据结构与NumPy基础 张量概念与线性代数基础 机器学习基本概念 第2-3周:PyTorch核心 张量操作与计算图 自动微分(autograd)机制 数据加载与预处理(DataLoader, Dataset) 构建神经网络模块(nn.Module) 第二阶段:深度学习基础(4-6周) 第4周:线性模型与优化器 线性回归与逻辑回归实现 优化器(SGD, Adam等) 损失函数与评估指标 第5-6周:基础神经网络 多层感知机(MLP) 卷积神经网络(CNN) 循环神经网络(RNN, LSTM, GRU) 第7-8周:训练技巧 模型保存与加载 学习率调度 正则化技术 迁移学习 第三阶段:进阶应用(6-8周) 第9-10周:计算机视觉 图像分类 目标检测 图像分割 视觉Transformer 第11-12周:自然语言处理 词嵌入 序列到序列模型 Transformer架构 预训练语言模型应用 第13-14周:生成模型 自编码器 变分自编码器(VAE) 生成对抗网络(GAN) 扩散模型 第四阶段:工程实践(4-6周) 第15-16周:模型部署 模型量化与优化 TorchScript与ONNX导出 服务化部署 移动端部署 第17-18周:高级训练技术 分布式训练 混合精度训练 梯度累积与梯度裁剪 模型剪枝与蒸馏 第19-20周:项目实战 完整项目流程 模型性能优化 工业级代码实践

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