0.6 模块与包管理

概述

你已经熟悉 ES6 的 import / export、CommonJS 的 require,以及 npm 的安装、锁文件、发布流程。Python 的模块系统语法相似但语义有差异,包管理用 pip/uv、pyproject.tomluv.lock。这一章把"导入语法"和"依赖管理"两块都对齐清楚。

JS/TS ↔ Python 对照表

概念

JavaScript / Node.js

Python

说明

导入整个模块

import * as x from 'x'

import x

命名空间导入

默认导入

import x from 'x'

无(用 import x

Python 无默认导出

具名导入

import { a, b } from 'x'

from x import a, b

语法不同

重命名导入

import { a as b }

from x import a as b / import x as y

都有 as

导出

export / export default

模块顶层名字自动可导入

无需 export 关键字

相对导入

import './foo'

from .foo import x / from ..pkg import x

点号语法

模块文件

foo.js

foo.py

.py 后缀

包(目录)

package.json 标识

__init__.py 文件标识

包 = 含 __init__.py 的目录

模块路径解析

node_modules 查找

sys.path 查找

搜索路径列表

默认导出对象

module.exports

无直接对应(模块对象)

见下文

动态导入

await import('x')

importlib.import_module

运行时动态加载

入口文件

main 字段 / index.js

if __name__ == "__main__":

入口守卫

循环依赖

支持(可能 partial)

支持(同样要小心)

都容易踩坑

依赖管理对照

概念

npm

Python (pip / uv)

安装依赖

npm install

uv sync / pip install

添加依赖

npm install <pkg>

uv add <pkg>

添加开发依赖

npm install -D <pkg>

uv add --dev <pkg>

移除依赖

npm uninstall <pkg>

uv remove <pkg>

全局安装 CLI

npm install -g <pkg>

uv tool install <pkg>

锁文件

package-lock.json

uv.lock

配置清单

package.json

pyproject.toml

发布

npm publish

uv publish

注册中心

npm registry

PyPI

私有源

.npmrc 配 registry

uv 配置 index / .pypirc

脚本

npm run <script>

无官方等价物(用 uv/just/make)

核心概念

1. import vs import / require

// JS:ES6
import _ from "lodash";
import { cloneDeep } from "lodash";
import * as utils from "./utils";

// JS:CommonJS
const fs = require("fs");
# Python
import lodash                      # 导入整个模块,用 lodash.xxx 访问
from lodash import cloneDeep       # 导入具体名字
import utils                       # 导入本地模块 utils.py
  • Python 没有 export 关键字:模块顶层定义的所有名字默认都可供导入(除了下划线开头的被视为内部)。

  • import x 类似 import * as xfrom x import y 类似 import { y } from 'x'

2. from x import y vs import { y } from ‘x’

// JS
import { useState, useEffect } from "react";
# Python
from react import useState, useEffect

语法几乎一一对应:from <模块> import <名字>import { <名字> } from '<模块>'

3. init.py:包标识

JS 里目录要成为包,需要有 package.json(或 index.js 约定)。Python 里,一个目录要成为包(package),需要包含一个 __init__.py 文件(Python 3.3+ 可以省略,但显式写更清晰)。

my_project/
├── package.json       →  my_project/
│                            ├── __init__.py      # 标识这是包
├── src/
│   ├── index.js
│   └── utils.js
# 目录结构
my_project/
├── __init__.py          # 包标识(可为空)
├── models.py
└── utils.py

# 导入
from my_project import utils
from my_project.models import User
  • __init__.py 在包被导入时执行,可以放初始化代码,也可以为空。

  • 相对导入用点号:from .utils import helper(当前包)、from ..sibling import x(上级包)。

4. pip/uv install vs npm install

# npm
npm install                 # 按 package.json 装全部依赖
npm install axios           # 添加一个依赖

# uv(现代推荐)
uv sync                     # 按 pyproject.toml 装全部依赖 + 建虚拟环境
uv add requests             # 添加一个依赖并更新锁文件
  • uv add 会自动更新 pyproject.tomldependenciesuv.lock,一步到位(对应 npm install <pkg> --save)。

  • uv add --dev pytest 加开发依赖。

5. 依赖锁定 uv.lock vs package-lock.json

两者理念一致:锁文件记录精确版本 + 依赖树哈希,保证团队环境可复现。

  • package-lock.jsonuv.lock(或 poetry.lock,或老式的 requirements.txt)。

  • requirements.txt 是历史遗留的"扁平版本清单",不锁传递依赖的完整树;uv.lock / poetry.lock 更严格(对应 package-lock.json)。

uv lock        # 生成/更新 uv.lock(类似 npm install --package-lock-only)
uv sync        # 按锁文件安装(类似 npm ci)

6. 虚拟环境隔离原理

你已经理解 node_modules/ 让每个项目的依赖互相隔离。Python 的隔离更彻底:

  • npm 把依赖装到项目内的 node_modules/,靠 PATH 找到可执行文件。

  • Python 的 venv 会创建一个独立的解释器副本 + 独立的 site-packages/ 目录,激活后 pythonpip 都指向这个环境,依赖也装到这里。

# Node
project/
└── node_modules/
    └── axios/

# Python(uv 默认放 .venv)
project/
├── .venv/
│   ├── Scripts/python.exe     # 独立解释器
│   └── Lib/site-packages/     # 独立依赖目录
│       └── requests/

7. 发布到 PyPI vs npm publish

# npm
npm login
npm publish

# uv / Python
uv build                      # 打包成 wheel/sdist
uv publish                   # 发布到 PyPI
  • PyPI(pypi.org)就是 Python 的 npm registry。

  • 包名规范:全小写 + 短横线(my-tool),导入名用下划线(my_tool)。

8. 私有源

# npm:.npmrc
# registry=https://registry.npmmirror.com/

# uv:命令行或 uv.toml
uv add --index https://pypi.org/simple my-pkg
# 或用环境变量 UV_INDEX_URL / 配置文件 uv.toml

代码示例

本地多模块项目结构对比

// utils.js
export function add(a, b) { return a + b; }
export const VERSION = "1.0";

// main.js
import { add, VERSION } from "./utils";
console.log(add(1, 2));
# utils.py
def add(a, b):
    return a + b

VERSION = "1.0"

# main.py
from utils import add, VERSION
print(add(1, 2))

入口守卫对比

// main.js
if (require.main === module) {
  run();
}
# main.py
def run():
    print("运行中")

if __name__ == "__main__":
    run()

动态导入对比

// JS
const mod = await import("./config");
# Python
import importlib
mod = importlib.import_module("config")

最佳实践

  • uv init / uv add 管理项目,锁文件 uv.lock 提交到版本库。

  • 导入具体名字用 from x import y,导入整个模块用 import x,避免 from x import *(命名污染,类似 import * as 的坏味道)。

  • 每个包目录都放一个 __init__.py(哪怕为空),意图明确。

  • 包内模块间用相对导入 from .foo import bar,别用绝对导入(重构时路径会断)。

  • 依赖装进虚拟环境,命令行工具用 uv tool install

  • 本地开发私有包用 uv add --path ../mylib(类似 npm link / file: 依赖)。

前端开发者常见陷阱

  1. 以为有 export 关键字:Python 不需要,模块顶层名字自动可导入。想隐藏就加下划线前缀 _private

  2. import xfrom x import y 搞混import os 之后要用 os.pathfrom os import path 之后才能直接用 path

  3. 模块名和变量名冲突:文件叫 json.py 会遮蔽标准库的 json,导致 import 出错(类似 JS 里覆盖全局)。

  4. 相对导入报错:直接运行一个包内模块(python pkg/mod.py)时相对导入会失败,因为 __name__ 不是包路径,需从项目根用 -m 运行(python -m pkg.mod)。

  5. 忘了虚拟环境:直接 pip install 装到全局,换项目后依赖混乱。

  6. requirements.txtpyproject.toml 二选一混淆:新项目用 pyproject.toml + uv.lockrequirements.txt 是旧方案。

  7. 循环依赖:A 导入 B、B 导入 A,运行时 AttributeError/ImportError,和 JS 的循环依赖一样要重构。

  8. 包目录缺少 __init__.py:某些工具/老代码会报找不到模块,显式加一个空文件最稳妥。

  9. sys.path 不懂:import 时 Python 从 sys.path 找模块,脚本所在目录默认在其中,但项目根不一定。这就是为什么运行路径不同会导致 import 失败。

  10. 把 npm 的 ^ ~ 版本语法照搬:Python 用 >===~=(兼容版本),语义不同。

总结

  • import ximport * as xfrom x import yimport { y } from 'x',无 export 关键字。

  • 包 = 含 __init__.py 的目录,相对导入用 ...

  • uv 是现代的包管理器:uv add/uv sync/uv remove 对应 npm 的 install 流程。

  • uv.lockpackage-lock.jsonpyproject.tomlpackage.json,PyPI ≈ npm registry。

  • 虚拟环境比 node_modules 隔离更彻底,连解释器一起隔离;新项目务必用 uv init 起步。