常用标准库:os、sys、pathlib

Python 标准库里有几个模块几乎每个项目都用得到:os 管系统与环境变量、sys 管 Python 解释器本身、pathlib 管路径。前面 文件读写操作 已经介绍过 pathlib 的基础。这一篇把三个模块日常最常用的功能串起来讲,让你以后写脚本时知道该找哪个

一、os 模块:跟系统打交道

环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
import os

# 读
os.environ["PATH"] # 存在则读;不存在抛 KeyError
os.environ.get("APP_ENV", "development") # 带默认值
os.getenv("APP_ENV", "development") # 同上,更常见

# 写(仅当前进程生效,退出即消失)
os.environ["APP_DEBUG"] = "1"

# 遍历
for key, value in os.environ.items():
print(key, value)

注意os.environ 的值永远是字符串。要 bool/int 自己转:

1
2
DEBUG = os.getenv("DEBUG", "0").lower() in ("1", "true", "yes")
PORT = int(os.getenv("PORT", 8080))

目录操作

1
2
3
4
5
6
7
8
os.getcwd()                          # 当前工作目录
os.chdir("/tmp") # 切换(用完记得切回)
os.listdir(".") # 列目录(返回文件名列表)
os.makedirs("a/b/c", exist_ok=True) # 递归建目录
os.mkdir("a") # 只建一层
os.rmdir("empty_dir") # 删空目录
os.rename("a.txt", "b.txt") # 重命名/移动
os.remove("file.txt") # 删文件

但现代 Python 推荐用 pathlib——见下一节。

os.path(老 API,逐步用 pathlib 取代)

1
2
3
4
5
6
7
8
9
os.path.join("a", "b", "c.txt")       # 'a/b/c.txt'
os.path.exists("file.txt")
os.path.isfile("file.txt")
os.path.isdir("dir")
os.path.abspath("relative") # → 绝对路径
os.path.dirname("/a/b/c.txt") # '/a/b'
os.path.basename("/a/b/c.txt") # 'c.txt'
os.path.splitext("c.txt") # ('c', '.txt')
os.path.getsize("file.txt") # 字节

同样功能 pathlib 全都有,代码更漂亮。

系统信息

1
2
3
4
5
os.cpu_count()                       # CPU 核心数
os.name # 'nt' (Windows) / 'posix' (Unix)
os.getpid() # 当前进程 PID
os.getppid() # 父进程 PID
os.getlogin() # 当前登录用户名

执行外部命令

1
2
3
4
5
6
os.system("ls -la")     # ❌ 老 API,不推荐(返回值只有退出码)

# ✅ 用 subprocess
import subprocess
result = subprocess.run(["ls", "-la"], capture_output=True, text=True)
print(result.stdout)

二、sys 模块:跟 Python 解释器打交道

命令行参数

1
2
3
4
5
import sys

sys.argv # 命令行参数列表,第一个是脚本名
# python script.py --name 咖飞 --age 3
# sys.argv = ['script.py', '--name', '咖飞', '--age', '3']

简单场景用 sys.argv,复杂用 argparse

1
2
3
4
5
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("input")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()

标准输入输出错误

1
2
3
4
5
6
7
8
9
10
sys.stdin       # 标准输入
sys.stdout # 标准输出
sys.stderr # 标准错误

# 打印到 stderr
print("错误", file=sys.stderr)

# 逐行读输入
for line in sys.stdin:
print(line.rstrip())

退出

1
2
3
sys.exit()        # 正常退出
sys.exit(1) # 退出码 1(表示错误)
sys.exit("错误信息") # 打到 stderr,退出码 1

注意sys.exit() 内部抛 SystemExit 异常。所以 try / except: 会把它捕获——但 except Exception: 不会(因为 SystemExit 继承自 BaseException)。

Python 版本 / 平台

1
2
3
4
5
6
7
sys.version              # '3.13.0 (main, ...)'
sys.version_info # sys.version_info(major=3, minor=13, ...)
sys.platform # 'win32' / 'linux' / 'darwin'
sys.executable # 当前 Python 解释器路径

if sys.version_info < (3, 10):
raise RuntimeError("需要 Python 3.10+")

模块搜索路径

1
2
3
sys.path                 # 模块搜索路径列表
sys.path.append("/new/path") # 添加(不推荐,用 pip install -e . 更好)
sys.modules # 已导入的模块字典

递归深度

1
2
sys.getrecursionlimit()          # 默认 1000
sys.setrecursionlimit(5000) # 加深,慎用(栈溢出崩溃)

内存与对象

1
2
sys.getsizeof(obj)       # 对象占用字节数(浅)
sys.getrefcount(obj) # 引用计数(调试用)

三、pathlib:面向对象的路径操作

前面 文件读写操作 简单讲过,这里补充完整常用 API。

1
2
3
4
5
6
from pathlib import Path

# 构造
p = Path("data/input.txt")
p = Path.home() / "Documents" / "notes.md"
p = Path.cwd() # 当前目录

属性

1
2
3
4
5
6
7
8
p.name          # 'input.txt'
p.stem # 'input'
p.suffix # '.txt'
p.suffixes # ['.txt'](多后缀 like .tar.gz)
p.parent # PosixPath('data')
p.parents # 迭代所有上层
p.parts # ('data', 'input.txt')
p.is_absolute()

判断

1
2
3
4
p.exists()
p.is_file()
p.is_dir()
p.is_symlink()

读写(一步)

1
2
3
4
5
Path("greeting.txt").write_text("你好", encoding="utf-8")
Path("greeting.txt").read_text(encoding="utf-8")

Path("img.png").write_bytes(b"...")
Path("img.png").read_bytes()

目录

1
2
3
4
5
6
7
8
9
10
11
12
Path("dir").mkdir(parents=True, exist_ok=True)   # 递归+幂等
Path("empty_dir").rmdir() # 删空目录

# 遍历目录
for f in Path("logs").iterdir():
print(f)

for f in Path("logs").glob("*.log"): # 一层通配
print(f)

for f in Path("logs").rglob("*.log"): # 递归所有子目录
print(f)

变换

1
2
3
4
5
6
p.with_name("new.txt")
p.with_suffix(".json")
p.with_stem("newstem")
p.absolute() # → 绝对路径
p.resolve() # → 规范化(解析符号链接)
p.relative_to("/root") # 相对路径

复制/移动

pathlib 没有直接的 copy/move,用 shutil

1
2
3
4
5
6
import shutil

shutil.copy(src, dst)
shutil.copytree(src_dir, dst_dir)
shutil.move(src, dst)
shutil.rmtree(dir) # 递归删(危险)

Path.rename(target) 但不能跨设备:

1
Path("a.txt").rename("b.txt")

四、__file__ 和脚本自身路径

在一个模块里知道自己的位置:

1
2
3
4
5
6
7
from pathlib import Path

# 脚本自身
HERE = Path(__file__).resolve().parent

# 相对脚本的路径
CONFIG_PATH = HERE / "config.json"

别用相对路径,否则脚本在别的目录启动就崩。永远基于 __file__ 拼绝对路径。

五、os + pathlib 混用

有些 os 的功能 pathlib 没有:

1
2
3
4
5
6
7
8
9
# 环境变量
os.environ

# 系统调用
os.getpid()

# 权限
p.stat().st_mode # 得到权限位
os.chmod(p, 0o755) # pathlib 3.11+ 有 p.chmod

六、临时文件与目录

1
2
3
4
5
6
7
8
9
10
11
import tempfile

# 临时文件(自动删除)
with tempfile.NamedTemporaryFile("w", suffix=".txt", encoding="utf-8", delete=True) as f:
f.write("temp")
print(f.name)

# 临时目录
with tempfile.TemporaryDirectory() as d:
Path(d, "hello.txt").write_text("hi")
# 出块时目录清空

七、常用工具集

配置项统一读取

1
2
3
4
5
6
7
8
9
10
11
def env(key, default=None, cast=str):
val = os.environ.get(key, default)
if val is None:
return None
if cast == bool:
return str(val).lower() in ("1", "true", "yes")
return cast(val)


PORT = env("PORT", 8080, int)
DEBUG = env("DEBUG", False, bool)

项目根目录

1
2
3
PROJECT_ROOT = Path(__file__).resolve().parent.parent
CONFIG_DIR = PROJECT_ROOT / "config"
DATA_DIR = PROJECT_ROOT / "data"

找所有 .py

1
2
for p in Path("src").rglob("*.py"):
print(p)

统计目录大小

1
2
def total_size(root: Path) -> int:
return sum(f.stat().st_size for f in root.rglob("*") if f.is_file())

八、一个完整例子:项目文件整理脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# organize.py
"""按扩展名把一个目录里的文件归类到子目录。"""

from pathlib import Path
import shutil
import sys


CATEGORIES = {
"images": {".jpg", ".jpeg", ".png", ".gif", ".webp"},
"documents": {".pdf", ".doc", ".docx", ".xlsx", ".txt", ".md"},
"videos": {".mp4", ".mov", ".avi", ".mkv"},
"audios": {".mp3", ".wav", ".flac", ".m4a"},
"archives": {".zip", ".tar", ".gz", ".7z", ".rar"},
"code": {".py", ".js", ".ts", ".go", ".rs", ".c", ".cpp"},
}


def category(ext: str) -> str:
for cat, exts in CATEGORIES.items():
if ext.lower() in exts:
return cat
return "others"


def organize(target: Path, dry_run: bool = False):
if not target.is_dir():
print(f"{target} 不是目录", file=sys.stderr)
sys.exit(1)

for f in target.iterdir():
if not f.is_file():
continue
cat = category(f.suffix)
dest_dir = target / cat
dest_dir.mkdir(exist_ok=True)
dest = dest_dir / f.name

if dry_run:
print(f"[干跑] {f}{dest}")
else:
print(f"{f}{dest}")
shutil.move(str(f), str(dest))


if __name__ == "__main__":
target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.home() / "Downloads"
dry_run = "--dry-run" in sys.argv
organize(target, dry_run=dry_run)

九、常见陷阱

  1. os.environ 值只是字符串:需要转 int/bool。
  2. os.chdir 副作用:全局改工作目录,多线程/协程时容易乱。
  3. 相对路径依赖启动目录:永远基于 __file__ 拼绝对路径。
  4. Path 拼接用 /:不是 +
  5. shutil.rmtree 是核弹:写错路径可能删掉整个磁盘。加确认或用 send2trash 库送回收站。
  6. sys.exit 抛异常:会被 try/except: 捕获,用 except Exception: 不会。
  7. Windows 路径与正斜杠:pathlib 会自动处理,但注意 str(p) 在 Windows 上会用反斜杠。

十、小结与延伸阅读

  • os:环境变量、进程、目录(旧 API);
  • sys:命令行参数、退出码、Python 版本、模块路径;
  • pathlib:面向对象的路径,优先用它取代 os.path
  • 复制/删目录用 shutil
  • 临时文件用 tempfile
  • 项目路径统一从 __file__ 出发;
  • 环境变量值都是字符串,手动转。

延伸阅读:

下一篇 常用标准库:collections 我们讲更高级的数据结构。