模块划命名空间,包环境划依赖边界。
module决定"哪些名字对外可见",Pkg 的环境决定"这个项目用哪些版本的哪些包"。两者配合,多项目依赖打架的问题在 Julia 里被结构性解决。
module Geometry export area, perimeter # 只有 export 出去的名字外界可见 struct Circle r::Float64 end area(c::Circle) = π * c.r^2 perimeter(c::Circle) = 2π * c.r internal_helper(x) = x + 1 # 未导出,模块私有 end # module
使用方式:
include 之后用 using using .Geometry # 点号表示当前会话里定义的模块 c = Circle(2.0) area(c) # 12.566... Geometry.internal_helper(1) # 未导出的名字要带模块前缀访问 import .Geometry: perimeter # 只引入单个名字的另一种写法
在 REPL 按 ] 进入包管理模式,动手走一遍项目生命周期:
] generate MyTools # 生成一个包骨架 ] activate . # 激活当前目录为项目环境 ] add DataFrames CSV # 依赖装进本项目,不污染全局 ] status # 查看当前环境的依赖清单 ] instantiate # 依据清单精确复原环境

💡 关键直觉:
Project.toml是"我声明要用什么",Manifest.toml是"实际解析到了什么"。提交代码库时前者必交,后者建议也交——这是科研代码可复现的关键。
| 命令 | 作用 |
|---|---|
add 包名 |
安装并写入依赖 |
add 包名@v1.2 |
安装指定版本 |
update |
升级所有依赖到兼容的最新版 |
rm 包名 |
移除依赖 |
activate 路径 |
切换项目环境 |
status |
列出当前环境依赖 |
pin 包名 |
锁定版本不随 update 变动 |
⚠️ 常见坑:在全局环境装了一堆包,然后项目里
using报"找不到"。原因是当前激活的是项目环境,全局的包对它不可见。先] status看清楚自己在哪个环境,再决定装在哪。
把流程走完整。背景:手头有一个做数据清洗的脚本,函数越加越多,同事想用,复制粘贴已经开始出错。第一步,在空目录 ] generate DataClean,得到骨架后把脚本里的函数搬进 src/DataClean.jl:
module DataClean export clean!, describe_col """去掉缺失值并把列统一为 Float64,返回有效行数。""" function clean!(v::AbstractVector{<:Real}) idx = findall(!ismissing, v) # 原地过滤演示:返回新数组更常见,这里为了示意! length(idx) end describe_col(v) = (最小=minimum(v), 最大=maximum(v), 均值=sum(v)/length(v)) end # module
第二步,同目录 ] activate . 再 ] add Statistics ] dev .,测试脚本里 using DataClean 即可调用。第三步是团队协作的关键操作:把 Project.toml 与 Manifest.toml 一起提交,同事拉下代码后只需 ] instantiate 就复原出完全一致的依赖版本——结果解读:从此"我机器上能跑"这句话在团队里消失了。变式:包成熟后可以加测试目录与 CI,3.2 到 8.3 之间会用到的 Test 标准库在这里就能先埋一颗种子。
第一条,export 克制。导出的名字就是这个模块的 API 面,导出越多,与其他包撞名风险越大;只导出用户真正需要的,内部函数一概前缀访问。第二条,别在模块顶层跑代码。顶层放常量与类型定义没问题,但顶层做 I/O 或长计算会把 using 变成一次漫长的等待,初始化逻辑放进 __init__ 函数。第三条,import 与 using 分工:using Foo 拿到全部导出名,import Foo: bar 只拿一个且可以继续给它挂方法,扩展第三方类型时必须用后者,这条规则直接连着 2.2 的内容。
环境问题占新手求助的一半,根源是没搞清"当前在哪、装到哪"。四种状态对照处理:REPL 启动默认在全局默认环境(提示符里不带项目名),这里装的包全机共享,试验可以、项目别用;activate . 后进入项目环境,提示符形如 (MyTools),此后 add 只写这个项目的清单;activate --temp 建立一次性临时环境,装什么关掉就消失,适合试用陌生包;activate 不带参数则退回默认环境。判断口诀是看提示符括号里的名字再动手,配套命令 ] status 能列出当前环境的全部直接依赖。另一个高频边界是包名冲突:两个包都导出 foo,using 两个包后裸用 foo 会报歧义警告,写全 PkgA.foo 即可消除,这不是错误而是命名空间机制在正常工作。
module + export 划定命名空间,未导出名字带前缀访问;activate 给项目独立环境,依赖互不打架;instantiate 是从清单到可用环境的一条命令复原;顺带一句关于命名空间的哲学:Julia 把"可见性"交给模块作者、把"解析权"交给使用者(using 全量、import 单点、前缀全名三档自选),比起全开放或全封闭的设计,这种分级让大型项目的依赖关系始终可追溯。理解了这一点,] status 的输出在你眼里就不再是一串包名,而是项目依赖边界的地图。