Python & AI Tutorials Logo
Python 编程

29. 与操作系统交互:sys 与 os

Python 程序并不是孤立存在的——它们运行在操作系统上,而操作系统负责管理文件、目录、环境设置,以及程序如何启动和通信。sysos 模块提供了与该环境交互的工具,让你的程序能够接收命令行参数、读取环境变量、在文件系统中导航,并创建或删除目录。

理解这些模块会把你的 Python 脚本从孤立的代码,变成能够集成到更广泛系统中的程序:它们可以在启动时响应用户输入,并以编程方式管理文件与文件夹。

29.1) 命令行参数与使用 sys.argv

当你从终端或命令提示符运行 Python 脚本时,可以向它传入额外信息——这些称为命令行参数(command-line arguments)。例如:

bash
python greet.py Alice 25

这里,greet.py 是脚本名称,Alice25 是传递给程序的参数。sys 模块通过 sys.argv 提供对这些参数的访问;它是一个列表(list),包含脚本名以及所有参数,并且都以字符串形式存储。

29.1.1) 什么是 sys.argv?

sys.argv 是一个列表,其中:

  • sys.argv[0] 总是正在运行的脚本名称
  • sys.argv[1]sys.argv[2] 等,是在脚本名之后传入的参数
  • 所有元素都是字符串,即使它们看起来像数字

我们来创建一个简单脚本看看它是如何工作的:

python
# show_args.py
import sys
 
print("Script name:", sys.argv[0])
print("Number of arguments:", len(sys.argv))
print("All arguments:", sys.argv)

如果你用下面方式运行该脚本:

bash
python show_args.py hello world 123

Output:

Script name: show_args.py
Number of arguments: 4
All arguments: ['show_args.py', 'hello', 'world', '123']

注意 sys.argv 包含 4 个元素:脚本名加上三个参数。数字 123 被存储为字符串 '123',而不是整数。

29.1.2) 在程序中使用命令行参数

命令行参数让用户无需修改代码就能自定义程序行为。下面是一个问候程序,它使用第一个参数作为名字:

python
# greet.py
import sys
 
if len(sys.argv) < 2:
    print("Usage: python greet.py <name>")
    sys.exit(1)  # 以错误码退出
 
name = sys.argv[1]
print(f"Hello, {name}!")

运行如下命令:

bash
python greet.py Alice

Output:

Hello, Alice!

如果你忘了提供名字:

bash
python greet.py

Output:

Usage: python greet.py <name>

程序会检查是否提供了足够的参数。如果没有,它会打印用法信息并通过 sys.exit(1) 退出。数字 1 是一个退出码(exit code)——按照约定,0 表示成功,非零值表示错误。这能帮助其他程序或脚本判断你的程序是否成功运行。

29.1.3) 将参数转换为其他类型

由于所有参数都是以字符串形式到达的,你通常需要进行类型转换。下面是一个计算矩形面积的程序:

python
# area.py
import sys
 
if len(sys.argv) < 3:
    print("Usage: python area.py <width> <height>")
    sys.exit(1)
 
try:
    width = float(sys.argv[1])
    height = float(sys.argv[2])
except ValueError:
    print("Error: Width and height must be numbers")
    sys.exit(1)
 
area = width * height
print(f"Area: {area}")

运行如下命令:

bash
python area.py 5.5 3.2

Output:

Area: 17.6

如果用户提供了无效输入:

bash
python area.py five three

Output:

Error: Width and height must be numbers

try-except 块(来自第 25 章)可以优雅地处理转换错误,提供有用反馈,而不是带着 traceback 崩溃退出。

用户运行:python script.py arg1 arg2

Python 创建 sys.argv 列表

sys.argv[0] = 'script.py'

sys.argv[1] = 'arg1'

sys.argv[2] = 'arg2'

脚本访问 sys.argv

将参数作为字符串处理

如有需要则转换类型

在程序逻辑中使用这些值

29.2) 使用 sys 获取解释器与运行时信息

sys 模块提供了关于 Python 解释器本身以及运行时环境的信息。这对调试(debugging)、日志记录(logging),或者编写能适配不同 Python 版本或平台的代码很有用。

29.2.1) Python 版本信息

sys.versionsys.version_info 会告诉你运行代码的 Python 版本:

python
import sys
 
print("Python version string:", sys.version)
print("Version info:", sys.version_info)
print(f"Major version: {sys.version_info.major}")
print(f"Minor version: {sys.version_info.minor}")

Output (example):

Python version string: 3.11.4 (main, Jul  5 2023, 13:45:01) [GCC 11.2.0]
Version info: sys.version_info(major=3, minor=11, micro=4, releaselevel='final', serial=0)
Major version: 3
Minor version: 11

sys.version 是人类可读的字符串,而 sys.version_info 是一个可通过程序进行比较的命名元组(named tuple):

python
import sys
 
if sys.version_info < (3, 8):
    print("This program requires Python 3.8 or higher")
    sys.exit(1)
 
print("Python version is compatible")

这能确保你的程序只在受支持的 Python 版本上运行。

29.2.2) 平台信息

sys.platform 用于标识操作系统:

python
import sys
 
print("Platform:", sys.platform)
 
if sys.platform == "win32":
    print("Running on Windows")
elif sys.platform == "darwin":
    print("Running on macOS")
elif sys.platform.startswith("linux"):
    print("Running on Linux")
else:
    print("Running on another platform")

Output (on Linux):

Platform: linux
Running on Linux

这让你在必要时可以编写特定平台代码,例如使用不同的文件路径或系统命令。

29.2.3) 模块搜索路径

sys.path 是一个目录列表,Python 在导入模块时会搜索这些目录:

python
import sys
 
print("Module search paths:")
for path in sys.path:
    print(f"  {path}")

Output (example):

Module search paths:
  /home/user/projects
  /usr/lib/python3.11
  /usr/lib/python3.11/lib-dynload
  /home/user/.local/lib/python3.11/site-packages

第一个条目通常是包含你的脚本的目录。当你使用 import 时,Python 会按顺序搜索这些路径。理解 sys.path 有助于调试导入错误,或添加自定义模块目录。

29.2.4) 使用退出码退出程序

我们已经见过使用 sys.exit() 来停止程序。你可以传入一个退出码来表示成功或失败:

python
import sys
 
def process_data(filename):
    try:
        with open(filename) as f:
            data = f.read()
        # 处理数据...
        return True
    except FileNotFoundError:
        print(f"Error: File '{filename}' not found", file=sys.stderr)
        return False
 
if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python script.py <filename>", file=sys.stderr)
        sys.exit(1)
    
    success = process_data(sys.argv[1])
    sys.exit(0 if success else 1)

退出码遵循 Unix 约定:

  • 0 表示成功
  • 非零值表示不同类型的错误
  • 其他程序可以检查这些退出码,以判断你的程序是否成功

29.3) 使用 os 访问环境变量

环境变量(environment variables) 是由操作系统或用户设置的键值对(key-value pairs),程序可以读取它们。它们用于配置、存储路径、API keys,以及其他不应硬编码的设置。

29.3.1) 读取环境变量

os 模块提供 os.environ,它是一个类似字典的对象,包含所有环境变量:

python
import os
 
# 获取特定环境变量
home = os.environ.get('HOME')  # 在 Unix/Linux/macOS 上
print(f"Home directory: {home}")
 
# 如果变量不存在则返回 None
api_key = os.environ.get('MY_API_KEY')
print(f"API key: {api_key}")

Output (on Linux):

Home directory: /home/alice
API key: None

你也可以提供一个默认值:

python
import os
 
# 获取带默认值的环境变量
log_level = os.environ.get('LOG_LEVEL', 'INFO')
print(f"Log level: {log_level}")

Output (if LOG_LEVEL not set):

Log level: INFO

29.3.2) 常见环境变量

不同操作系统会提供标准环境变量:

python
import os
 
# 跨平台变量
print("Path:", os.environ.get('PATH'))
 
# Unix/Linux/macOS
print("User:", os.environ.get('USER'))
print("Home:", os.environ.get('HOME'))
print("Shell:", os.environ.get('SHELL'))
 
# Windows
print("User", os.environ.get('USERNAME'))
print("User Profile:", os.environ.get('USERPROFILE'))
print("Temp:", os.environ.get('TEMP'))

PATH 尤其重要——它列出了系统用来查找可执行程序的目录。当你输入 python 这样的命令时,系统会在这些目录中搜索。

29.3.3) 设置环境变量

你可以修改环境变量,以作用于你的程序以及它创建的任何子进程(subprocesses):

python
import os
 
# 设置一个环境变量
os.environ['MY_CONFIG'] = 'production'
 
# 再读回来
print(os.environ.get('MY_CONFIG'))  # Output: production
 
# 删除一个环境变量
del os.environ['MY_CONFIG']

重要:对 os.environ 的更改只会影响当前 Python 进程及其启动的任何程序。它们不会在程序退出后持久化,也不会影响其他程序。

29.4) 使用文件路径与目录(os.path、os.getcwd)

正确管理文件路径对于处理文件的程序至关重要。osos.path 模块提供了以平台无关(platform-independent)方式构建、操作和查询路径的工具。

29.4.1) 获取当前工作目录

当前工作目录(current working directory, CWD) 是你的程序把相对路径视为起点的文件夹:

python
import os
 
cwd = os.getcwd()
print(f"Current working directory: {cwd}")

Output (example):

Current working directory: /home/alice/projects/myapp

当你用相对路径(如 'data.txt')打开文件时,Python 会在当前工作目录中查找。理解 CWD 有助于调试 “file not found” 这类错误。

29.4.2) 更改当前目录

你可以使用 os.chdir() 更改工作目录:

python
import os
 
original = os.getcwd()
print("Original directory:", original)
 
# 切换到不同目录
os.chdir('/tmp')
print("New directory:", os.getcwd())
 
# 切回去
os.chdir(original)
print("Back to:", os.getcwd())

Output:

Original directory: /home/alice/projects
New directory: /tmp
Back to: /home/alice/projects

注意:在第 28 章中,你学习了 contextlib.chdir(),它会自动恢复原目录。对于简单的目录切换,更推荐使用上下文管理器(context manager):

python
from contextlib import chdir
 
with chdir('/tmp'):
    print("Temporarily in:", os.getcwd())
# 自动恢复

这能确保即使发生错误,目录也总会被恢复。

29.4.3) 使用 os.path.join() 构建路径

不同操作系统使用不同的路径分隔符(path separators):

  • Unix/Linux/macOS:/(正斜杠)
  • Windows:\(反斜杠)

os.path.join() 会为当前平台正确构建路径:

python
import os
 
# 构建子目录中文件的路径
data_dir = 'data'
filename = 'users.txt'
filepath = os.path.join(data_dir, filename)
 
print(f"File path: {filepath}")

Output (on Unix/Linux/macOS):

File path: data/users.txt

Output (on Windows):

File path: data\users.txt

你可以拼接多个组件:

python
import os
 
base = '/home/alice'
project = 'myapp'
subdir = 'data'
file = 'config.json'
 
full_path = os.path.join(base, project, subdir, file)
print(full_path)  # Output: /home/alice/myapp/data/config.json

使用 os.path.join() 能让你的代码在不同操作系统之间具有可移植性(portable)。

29.4.4) 检查路径是否存在

在操作文件或目录之前,先检查它们是否存在:

python
import os
 
path = 'data.txt'
 
if os.path.exists(path):
    print(f"'{path}' exists")
else:
    print(f"'{path}' does not exist")

你也可以更具体地检查文件或目录:

python
import os
 
path = 'mydir'
 
if os.path.isfile(path):
    print(f"'{path}' is a file")
elif os.path.isdir(path):
    print(f"'{path}' is a directory")
elif os.path.exists(path):
    print(f"'{path}' exists but is neither a file nor directory")
else:
    print(f"'{path}' does not exist")

这些检查能避免在尝试打开不存在的文件,或列出不存在的目录时出错。

29.4.5) 获取绝对路径

os.path.abspath() 会把相对路径转换为绝对路径:

python
import os
 
relative_path = 'data/users.txt'
absolute_path = os.path.abspath(relative_path)
 
print(f"Relative: {relative_path}")
print(f"Absolute: {absolute_path}")

Output (example):

Relative: data/users.txt
Absolute: /home/alice/projects/myapp/data/users.txt

这对日志记录、错误信息,或者当你需要知道文件的精确位置时很有用。

29.4.6) 将路径拆分为组件

os.path.split() 会把路径分成目录和文件名:

python
import os
 
path = '/home/alice/projects/data.txt'
directory, filename = os.path.split(path)
 
print(f"Directory: {directory}")
print(f"Filename: {filename}")

Output:

Directory: /home/alice/projects
Filename: data.txt

os.path.basename() 只获取文件名,而 os.path.dirname() 只获取目录:

python
import os
 
path = '/home/alice/projects/data.txt'
 
print(f"Basename: {os.path.basename(path)}")  # Output: data.txt
print(f"Dirname: {os.path.dirname(path)}")    # Output: /home/alice/projects

29.4.7) 拆分文件扩展名

os.path.splitext() 会将文件名与其扩展名分离:

python
import os
 
filename = 'report.pdf'
name, extension = os.path.splitext(filename)
 
print(f"Name: {name}")        # Output: report
print(f"Extension: {extension}")  # Output: .pdf

这对按文件类型处理文件很有用:

python
import os
 
files = ['data.csv', 'image.png', 'document.txt', 'script.py']
 
for file in files:
    name, ext = os.path.splitext(file)
    if ext == '.csv':
        print(f"Process CSV file: {file}")
    elif ext == '.png':
        print(f"Process image file: {file}")

Output:

Process CSV file: data.csv
Process image file: image.png

29.5) 列出、创建与删除文件和目录

os 模块提供了操作文件系统的函数:列出目录内容、创建新目录,以及删除文件和文件夹。

29.5.1) 列出目录内容

os.listdir() 会返回目录中所有项目的列表:

python
import os
 
# 列出当前目录的内容
contents = os.listdir('.')
print("Current directory contents:")
for item in contents:
    print(f"  {item}")

Output (example):

Current directory contents:
  script.py
  data.txt
  mydir
  README.md

该列表同时包含文件和目录。要区分它们,可以这样做:

python
import os
 
contents = os.listdir('.')
print("Files:")
for item in contents:
    if os.path.isfile(item):
        print(f"  {item}")
 
print("\nDirectories:")
for item in contents:
    if os.path.isdir(item):
        print(f"  {item}")

Output:

Files:
  script.py
  data.txt
  README.md
 
Directories:
  mydir

29.5.2) 创建目录

os.mkdir() 创建单个目录:

python
import os
 
new_dir = 'output'
 
if not os.path.exists(new_dir):
    os.mkdir(new_dir)
    print(f"Created directory: {new_dir}")
else:
    print(f"Directory already exists: {new_dir}")

Output:

Created directory: output

重要:如果父目录不存在,os.mkdir() 会失败。例如,当 'data' 不存在时尝试创建 'data/output' 将会抛出错误。

os.makedirs() 会创建所有必要的父目录:

python
import os
 
nested_dir = 'data/processed/2024'
 
if not os.path.exists(nested_dir):
    os.makedirs(nested_dir)
    print(f"Created directory structure: {nested_dir}")

Output:

Created directory structure: data/processed/2024

如果它们不存在,这将创建 data,然后 data/processed,再然后 data/processed/2024

29.5.3) 删除文件

os.remove() 删除一个文件:

python
import os
 
filename = 'temp.txt'
 
# 创建一个临时文件
with open(filename, 'w') as f:
    f.write('Temporary data')
 
print(f"File exists: {os.path.exists(filename)}")  # Output: True
 
# 删除文件
os.remove(filename)
print(f"File exists: {os.path.exists(filename)}")  # Output: False

警告os.remove() 会永久删除文件——它们不会进入回收站。

29.5.4) 删除目录

os.rmdir() 删除一个空目录:

python
import os
 
directory = 'empty_dir'
 
# 创建并删除一个空目录
os.mkdir(directory)
print(f"Created: {directory}")
 
os.rmdir(directory)
print(f"Removed: {directory}")

如果目录包含文件,os.rmdir() 会失败。要删除一个目录及其所有内容,你需要先删除文件:

python
import os
 
def remove_directory_contents(directory):
    """删除目录中的所有文件,然后删除该目录。
    
    注意:如果目录包含子目录则会失败。
    """
    if not os.path.exists(directory):
        print(f"Directory does not exist: {directory}")
        return
    
    # 删除目录中的所有文件
    for item in os.listdir(directory):
        item_path = os.path.join(directory, item)
        if os.path.isfile(item_path):
            os.remove(item_path)
            print(f"Removed file: {item_path}")
    
    # 删除现在已为空的目录
    os.rmdir(directory)
    print(f"Removed directory: {directory}")
 
# 示例用法
test_dir = 'test_data'
os.makedirs(test_dir, exist_ok=True)
 
# 创建一些测试文件
with open(os.path.join(test_dir, 'file1.txt'), 'w') as f:
    f.write('test')
with open(os.path.join(test_dir, 'file2.txt'), 'w') as f:
    f.write('test')
 
# 删除所有内容
remove_directory_contents(test_dir)

Output:

Removed file: test_data/file1.txt
Removed file: test_data/file2.txt
Removed directory: test_data

注意:对于更复杂的目录删除(包括子目录),Python 的 shutil 模块提供了 shutil.rmtree(),但这超出了我们当前的范围。

29.5.5) 重命名文件与目录

os.rename() 用于重命名或移动文件与目录:

python
import os
 
# 重命名文件
old_name = 'draft.txt'
new_name = 'final.txt'
 
# 创建测试文件
with open(old_name, 'w') as f:
    f.write('content')
 
os.rename(old_name, new_name)
print(f"Renamed '{old_name}' to '{new_name}'")

你也可以把文件移动到不同目录:

python
import os
 
# 创建目录和文件
os.makedirs('source', exist_ok=True)
os.makedirs('destination', exist_ok=True)
 
with open('source/file.txt', 'w') as f:
    f.write('content')
 
# 将文件移动到不同目录
os.rename('source/file.txt', 'destination/file.txt')
print("Moved file to destination directory")

sysos 模块让你的 Python 程序具备与操作系统交互的能力:接收命令行输入、读取配置,以及管理文件与目录。这些能力会把简单脚本转变为强大的命令行工具,使其能够与更广泛的系统环境无缝集成。

© 2025. Primesoft Co., Ltd.
support@primesoft.ai