Skip to content

Python 是什么与环境

Shell 适合把现成命令串起来,Python 更适合把数据、分支、异常、报告组织成清楚的脚本。脚本一旦开始出现 JSON 解析、接口调用、失败重试、批量处理这种逻辑,继续用 Shell 拼下去就会越来越难改——Python 的位置大概就在这。命令照样可以交给系统工具执行,判断、整理、记录、输出这些交给 Python。

写 Python 脚本其实就涉及三个对象:.py 文件、Python 解释器、依赖环境。很多"本地能跑、定时任务里失败"的问题,根子都出在这三者没对齐

.py 文件只是文本,操作系统不能直接理解里面的 Python 语法——负责执行代码的是解释器。解释器再去加载标准库和第三方包,最后调用文件、网络、进程这些系统能力。把这个链路搞清楚,后面"为什么找不到模块""为什么版本不对"这些问题就有排查方向了。

一、环境检查

写脚本前先看机器上有哪些 Python 命令,以及它们分别指向哪里。

Linux:

bash
python --version
python3 --version
which python
which python3

Windows PowerShell:

powershell
py -0p
py -3 --version
py -3 -m pip --version

python 这个命令在不同机器上含义不稳定。老系统里它可能指向 Python 2,也可能根本不存在。所以服务器脚本里更稳的写法是用 python3uv run python,或者直接写虚拟环境里的解释器路径——别假设 python 一定是 Python 3。

常见问题按三层看:

层面现象检查入口
解释器python 找不到、版本太旧、f-string 报语法错误python3 --versionwhich python3
依赖包ModuleNotFoundError: No module named ...uv pip list、虚拟环境路径
系统能力文件没权限、命令不存在、端口不通文件权限、PATH、防火墙、系统日志

二、解释器安装

Python 3 是现在的默认选择。脚本里只要用了 f-string、类型注解、新版标准库功能,解释器版本太低就会直接报语法错误,所以版本要先确认对。

Rocky / RHEL / CentOS:

bash
# 安装解释器和传统 pip 工具
dnf install -y python3 python3-pip

python3 --version
python3 -m pip --version

Ubuntu / Debian:

bash
apt-get update

# python3-venv 用于创建虚拟环境
apt-get install -y python3 python3-venv python3-pip

python3 --version
python3 -m pip --version

Windows 通常用 python.org 安装包或 winget。安装包里的"Add Python to PATH"影响 PowerShell 能不能直接找到 python 命令——这个勾选项很多人漏勾,装完发现命令找不到。已经装了但命令找不到时,先用 py -0p 看 Python 启动器能不能找到解释器。

三、依赖隔离

Python 自带一部分库,叫标准库。文件路径、JSON、日志、子进程这些能力标准库里就有:pathlib(路径拼接、判断文件)、json(读写 JSON)、logging(带时间和级别的日志)、subprocess(调用系统命令)、shutil(复制文件、查磁盘)、configparser(读 ini 配置)。

还有一些能力不在标准库里——HTTP 请求、SSH、MySQL、Redis、YAML 这些,要装第三方包:requests(调 HTTP API)、paramiko(SSH)、pymysql(连 MySQL)、redis(连 Redis)、pyyaml(处理 YAML)。

这里有个特别容易踩的坑:所有包都装进系统 Python,不同脚本的依赖版本会互相打架。脚本 A 需要 requests 2.28,脚本 B 升级到 requests 3.0——pip install 会直接覆盖,脚本 A 可能因为 API 变化跑不起来。系统自带的 Python 版本还可能有约束(比如 CentOS 7 的 yum 依赖系统 Python 2),往里面装包风险更大。

虚拟环境就是解决这个问题的:给每个项目单独准备一套包目录,脚本用哪个解释器启动,就从哪个环境里找包

虚拟环境不是另一个操作系统,只是一套独立的 Python 包目录。这个概念理解了,后面环境相关的坑就少一半。

四、项目依赖管理

传统做法是 python3 -m venvpip installrequirements.txt 加手动 activate这套流程的常见问题是:依赖装到哪个环境不清楚、换机器后版本不一致、cron 或 systemd 里没加载虚拟环境

uv 把这些事整合到一个入口里:uv add 写依赖并更新锁文件、uv run 用项目环境执行脚本、uv sync 在新机器上恢复环境。机器上先装 uv:

bash
# 安装脚本会提示 uv 被放到哪个用户目录
curl -LsSf https://astral.sh/uv/install.sh | sh

uv --version

Windows PowerShell:

powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

uv --version

装完提示 uv: command not found,通常是终端还没刷新 PATH。重新打开终端,或者按安装输出把 uv 所在目录加进 PATH。

一个最小脚本项目长这样:

text
todo-tools/
├── pyproject.toml
├── uv.lock
└── scripts/
    └── count.py

pyproject.toml 记录项目和依赖,uv.lock 锁定具体版本,scripts/ 放脚本文件。换机器运行时,锁文件能减少"同一个包不同版本导致行为不一样"的问题。

创建项目并装依赖:

bash
uv init todo-tools
cd todo-tools

# requests 会写入 pyproject.toml,并安装到项目虚拟环境
uv add requests

# 用项目环境执行 Python
uv run python --version
uv pip list

传统虚拟环境也能干同样的事:

bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install requests

区别在于 uv 项目里通常不用手动 activate,直接 uv run 就进项目环境。cron 或 systemd 里执行脚本时这点特别有用——非交互环境不会自动加载交互式终端里的虚拟环境,用 uv run 能避免"手动跑没问题、定时任务里找不到包"的经典坑。

五、包索引配置

访问 PyPI 慢或超时时,给 pip 或 uv 配个国内镜像。

pip 临时用镜像:

bash
python3 -m pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple

pip 用户级配置:

bash
python3 -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
python3 -m pip config set global.timeout 60
python3 -m pip config list

uv 临时用镜像:

bash
uv add requests --default-index https://pypi.tuna.tsinghua.edu.cn/simple

uv 当前 shell 临时生效:

bash
export UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
uv add requests

Windows PowerShell:

powershell
$env:UV_DEFAULT_INDEX = "https://pypi.tuna.tsinghua.edu.cn/simple"
uv add requests

常用镜像源:清华 https://pypi.tuna.tsinghua.edu.cn/simple、阿里云 https://mirrors.aliyun.com/pypi/simple/、腾讯云 https://mirrors.cloud.tencent.com/pypi/simple、华为云 https://repo.huaweicloud.com/repository/pypi/simple

https 镜像源通常不需要额外设 trusted-host。公司内部 http 源或证书异常的源,才会涉及证书和信任配置。

六、入口结构

一个 .py 文件通常把主要流程放进 main()。先看一个最简单的:

新增文件 scripts/hello.py:

python
#!/usr/bin/env python3
"""最小 Python 脚本入口。"""


def main():
    # 主要逻辑放在 main 里,之后更容易拆函数和测试
    print("hello python")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

几个部分解释一下:

  • #!/usr/bin/env python3 是 shebang。Linux 上给脚本加执行权限后,系统按这一行找解释器
  • def main() 把主流程收进函数,后面脚本变长了好拆分
  • if __name__ == "__main__": 区分直接执行和被别的文件 import——直接执行时进入 main(),被 import 时只加载函数不自动跑主流程
  • raise SystemExit(main()) 用 main 的返回值当退出码
bash
chmod +x scripts/hello.py
./scripts/hello.py

# 或者直接用解释器跑
uv run python scripts/hello.py

if __name__ == "__main__": 这个写法一开始看着别扭,但要养成习惯。脚本拆成多个文件后,它能避免"import 一个模块结果主流程被意外执行"的麻烦。

七、第一个脚本

用一个中性例子把前面串起来:读一份待办清单文本文件,统计有多少条任务。这个脚本只用标准库,适合当起点。

先准备输入文件 todo.txt:

text
买菜
写周报
回复邮件

跑步

中间有个空行,统计时应该跳过。

新增文件 scripts/count.py:

python
#!/usr/bin/env python3
"""统计待办清单里有多少条任务(跳过空行)。"""

from pathlib import Path
import sys


def count_todos(path):
    text = path.read_text(encoding="utf-8")

    count = 0
    for line in text.splitlines():
        if line.strip():  # 跳过空行
            count += 1

    return count


def main():
    path = Path("todo.txt")

    if not path.exists():
        print(f"文件不存在: {path}", file=sys.stderr)
        return 1

    total = count_todos(path)
    print(f"待办清单共 {total} 条")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

运行:

bash
uv run python scripts/count.py
echo $?

退出码 0 表示正常,非零表示异常。cron、systemd、CI、监控探针都靠退出码判断脚本结果——脚本只打印"文件不存在",但退出码还是 0,外层系统就会把失败当成成功。这个"退出码要和结果一致"的习惯,从第一个脚本就该养成。