CPython Windows 平台实战手册:从命令行运行、.pyd 扩展模块到嵌入式 Python 的完整解析
本文基于 CPython 官方文档 Doc/faq/windows.rst(Python on Windows FAQ)展开,系统讲解在 Windows 上运行 Python 程序、让脚本可直接执行、排查启动缓慢问题、理解 .pyd 扩展模块、向 Windows 应用嵌入 Python 解释器以及处理 CRT 缺失错误等核心问题,并结合 msvcrt 模块源码、freeze 打包工具 等仓库实现给出源码级佐证。读完本文,你将能够独立完成 Windows 环境下 Python 的运行、部署、嵌入与常见疑难故障的排查。
一、在 Windows 上运行 Python 程序
1.1 打开命令提示符
除非使用集成开发环境,否则你需要在 Windows 的"命令提示符"窗口(Command prompt)中键入命令。通常可以通过系统搜索栏搜索 cmd 打开它。窗口打开后你会看到一个命令行提示符,例如:
C:\>
盘符和提示符内容取决于你的系统配置,也可能是:
D:\YourName\Projects\Python>
1.2 理解解释器的工作方式
Python 脚本必须由另一个程序——Python 解释器——来处理。解释器读取你的脚本,将其编译为字节码(bytecodes),然后执行这些字节码来运行你的程序。官方 FAQ 强调,这一步的关键是让命令窗口识别 py 这个词作为启动解释器的指令。
1.3 用 py 启动交互式解释器
打开命令窗口后,输入 py 并回车:
C:\Users\YourName> py
你应该会看到类似如下输出:
Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:04:45) [MSC v.1900 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
此时解释器已进入交互式模式(interactive mode):你可以直接键入 Python 语句或表达式,边输入边执行或求值。官方 FAQ 称这是 Python 最强的特性之一。试着输入几个表达式看看结果:
>>> print("Hello")
Hello
>>> "Hello" * 3
'HelloHelloHello'
很多人把交互模式当作一个"方便且高度可编程的计算器"使用。当你要结束交互会话时,调用 exit() 函数,或者按住 Ctrl 键的同时输入 Z 再按 Enter,即可返回 Windows 命令提示符。
另外,你可能还会看到开始菜单中的 开始 --> 程序 --> Python 3.x --> Python (command line) 条目,它也会在新窗口中显示 >>> 提示符。注意:在该窗口中调用 exit() 或输入 Ctrl-Z 后窗口会直接消失——这是正常现象,因为 Windows 只是在该窗口中运行了一条 "python" 命令,解释器终止后窗口随之关闭。
1.4 运行你的 Python 脚本
确认 py 命令可用后,只需把脚本路径交给它。你需要给出脚本的绝对路径或相对路径。假设你的脚本 hello.py 放在桌面上,而命令提示符当前位于你的主目录:
C:\Users\YourName> py Desktop\hello.py
hello
二、如何让 Python 脚本可直接执行
在 Windows 上,标准的 Python 安装程序已经把 .py 扩展名与一个文件类型(Python.File)关联起来,并为该文件类型配置了打开命令,形如:
D:\Program Files\Python\python.exe "%1" %*
这已经足够让你在命令提示符中以 foo.py 的形式直接执行脚本。如果你希望省略扩展名、直接输入 foo 就能执行,则需要把 .py 添加到 PATHEXT 环境变量中。
三、为什么 Python 有时启动非常慢?
通常情况下 Python 在 Windows 上启动很快,但偶尔会出现"突然启动变得很慢"的故障报告,且同一配置的其他 Windows 机器上运行完全正常,这让问题更加令人困惑。
官方 FAQ 给出的结论是:问题很可能出在病毒查杀软件的配置上。一些杀毒软件在被配置为监控文件系统的所有读取活动时,会引入高到两个数量级的启动开销。建议在故障机器上检查杀毒软件的配置,确认它与正常机器确实配置一致。官方点名指出,McAfee 在配置为扫描全部文件系统读取活动时是典型的"重灾区"。
四、如何从 Python 脚本生成可执行文件?
如果你想要的是"用户下载后即可运行、无需先安装 Python 发行版"的独立程序,并不真的需要把 Python 编译成 C 代码。有一系列工具会分析程序所需模块集合,把这些模块与 Python 二进制绑定,生成单个可执行文件。
官方 FAQ 引用的工具清单见 Doc/faq/programming.rst 中的"如何创建独立二进制"小节,其内容包括:
- freeze 工具:包含在本仓库源码树的 Tools/freeze 目录中(含 freeze.py、makefreeze.py、makeconfig.py 等脚本)。它的工作原理是递归扫描源码中的
import语句,在标准 Python 路径和源码目录中寻找模块,把 Python 模块的字节码转换为 C 代码(可转换为 code 对象的数组初始化器,依赖 marshal 格式),生成只包含实际用到的内置模块的定制配置文件,最后编译生成的 C 代码并与解释器其余部分链接,形成一个与你的脚本行为完全一致的自包含二进制。 - Nuitka(跨平台)等第三方打包工具,可在 programming.rst 对应的条目中查阅完整列表。
五、.pyd 文件就是 DLL 吗?
是的,.pyd 文件本质上就是 DLL,但存在几处关键差异,理解这些差异对开发 C 扩展至关重要:
- 入口函数要求:如果你有一个名为
foo.pyd的 DLL,那么它必须提供一个PyInit_foo()函数。之后你只需在 Python 中写import foo,Python 会搜索foo.pyd(同时也搜索foo.py、foo.pyc),找到后尝试调用PyInit_foo()来初始化该模块。 - 无需链接你的 .exe 到 foo.lib:如果你把 .exe 与
foo.lib链接,Windows 会要求该 DLL 必须存在;而.pyd可以"缺席"——只有当你真正import foo时才需要它。 - 搜索路径不同:
foo.pyd的搜索路径是PYTHONPATH,而不是 Windows 搜索foo.dll所用的路径(系统 DLL 搜索顺序)。 - 导出机制不同:普通 DLL 中,符号通过源码中的
__declspec(dllexport)声明导出;而在.pyd中,可用函数的列表由模块定义决定(即PyModuleDef机制)。
这些机制在仓库中可以得到印证:
- 从源码结构看,CPython 构建系统明确把
.pyd作为 Windows 平台的扩展模块后缀:PC/layout/support/builddetails.py 中定义了"extension_suffix": ".pyd"及平台化后缀规则(如*.cp3x-win_amd64.pyd的命名模式,见 PC/layout/main.py); - Doc/extending/windows.rst 专门讲解了在 Windows 上构建扩展模块,其中详细区分了静态库(类似 Unix 的
.a)与导入库(import library)(.lib,只含符号信息、供链接器建立查找表),并给出cl /LD编译.dll时同时生成.lib的完整流程,是理解本节内容的最佳延伸阅读; - PC/crtlicense.txt 也说明每个
.exe、.dll和.pyd文件都会内嵌 Microsoft CRT 许可文本。
六、如何把 Python 嵌入 Windows 应用?
官方 FAQ 把在 Windows 应用中嵌入 Python 解释器归纳为 6 个要点,前两条被称为"两个关键而未写入手册的事实"。
6.1 不要直接把 Python 编进 .exe(关键事实一)
在 Windows 上,Python 必须作为 DLL 存在,这样才能导入本身就是 DLL 的模块。正确做法是链接 python{NN}.dll(NN 是版本号数字,如 Python 3.3 对应 "33")。有两种链接方式:
- 加载时链接(load-time linking):链接
python{NN}.lib(即python{NN}.dll对应的"导入库",仅为链接器定义符号); - 运行时链接(run-time linking):链接
python{NN}.dll,一切都在运行时发生,大幅简化链接选项。你的代码必须用 Windows 的LoadLibraryEx()加载python{NN}.dll,并通过GetProcAddress()取得 C API 函数指针;用宏可以让这些指针对调用 C API 的 C 代码"透明"。
6.2 用 SWIG 生成扩展模块(关键事实二)
如果使用 SWIG,可以很容易地创建一个 Python 扩展模块,把应用的数据和方法暴露给 Python。SWIG 会替你处理几乎所有繁琐细节,产物是直接链入你的 .exe(而不是 DLL)的 C 代码,这同样简化了链接。SWIG 会生成一个初始化函数,其名称取决于扩展模块名:模块名为 leo 时生成 initleo();如果使用了 SWIG shadow classes(推荐做法),则生成 initleoc(),用于初始化一个由 shadow class 使用的"几乎隐藏"的辅助类。
调用这个初始化函数等价于把模块导入 Python——这就是可以把 C 代码直接链入 .exe 的原因。
6.3 初始化代码示例
官方 FAQ 给出的最小初始化代码如下:
#include <Python.h>
...
Py_Initialize(); // 初始化 Python。
initmyAppc(); // 初始化(导入)辅助类。
PyRun_SimpleString("import myApp"); // 导入 shadow class。
6.4 多编译器环境的两个坑
如果你的应用使用的编译器与构建 pythonNN.dll 的 MSVC 不同,会暴露 C API 的两个问题:
问题一:接收 FILE * 参数的"超高层"函数在多编译器环境下无法工作,因为每个编译器对 struct FILE 的定义都不同。从实现角度看,这些函数其实非常底层。
问题二:SWIG 为 void 函数生成的包装代码如下:
Py_INCREF(Py_None);
_resultobj = Py_None;
return _resultobj;
问题在于 Py_None 是一个宏,展开后引用 pythonNN.dll 内部名为 _Py_NoneStruct 的复杂数据结构——在其他编译器下这段代码会失败。官方建议替换为:
return Py_BuildValue("");
也可以尝试用 SWIG 的 %typemap 指令自动完成该替换(原文作者自述尚未成功)。这一宏展开在本仓库中同样可见:Include/object.h 中在非 Windows 构建分支下正是 # define Py_None (&_Py_NoneStruct),与文档描述完全吻合。
6.5 关于解释器窗口的建议
用 Python 壳脚本在 Windows 应用内部弹出解释器窗口不是好主意——产生的窗口会独立于你的应用窗口系统。正确做法是你自己(或 wxPythonWindow 类)创建一个"原生"解释器窗口并把它连接到 Python 解释器。由于 Python 的输入/输出可以重定向到任何支持 read 和 write 的对象,你只需要在扩展模块中定义一个包含 read() 和 write() 方法的 Python 对象即可。
七、如何防止编辑器向 Python 源码中插入 Tab?
官方 FAQ 不推荐使用 Tab,Python 风格指南 PEP 8 建议分发的 Python 代码使用 4 个空格,这也是 Emacs python-mode 的默认行为。在任何编辑器中混用 Tab 和空格都是坏主意,MSVC 也不例外:进入 工具 --> 选项 --> 选项卡,把文件类型 "Default" 的"选项卡大小"和"缩进大小"都设为 4,并选中"插入空格"单选项。
当混用 Tab 与空格导致行首空白出错时,Python 会抛出 IndentationError 或 TabError 异常。你也可以批量检查整个目录树:仓库中的 Lib/tabnanny.py 就是 tabnanny 模块的实现,以批处理方式扫描目录中所有 .py 文件的缩进问题。
八、如何不阻塞地检测按键?
使用 msvcrt 模块——这是一个标准的 Windows 专用扩展模块。它定义了:
kbhit():检查是否有键盘按键待处理(不阻塞);getch():读取一个字符且不回显。
在仓库中,该模块的实现是 PC/msvcrtmodule.c,文件头注释明确说明它是"MS VC++ 运行时的 Python 接口,仅提供 MS 编译器下可用",且只使用 MS 编译器编译。其中 kbhit() 的实现直接包装 CRT 的 _kbhit()(见 msvcrt_kbhit_impl),getch() 包装 _getch()(见 msvcrt_getch_impl)。完整的 API 文档(包括宽字符版本 getwch、getche、文件加锁 locking、open_osfhandle 等)见 Doc/library/msvcrt.rst。文档还提醒:普通 API 只处理 ASCII,对国际化应用应尽可能使用宽字符 API。
九、如何解决缺少 api-ms-win-crt-runtime-l1-1-0.dll 的错误?
该错误可能出现在 Python 3.5 及以后版本运行于未安装全部更新的 Windows 8.1 或更早系统时。官方 FAQ 的处理步骤是:
- 首先确认你的操作系统仍在受支持范围内,并且已经更新到最新状态;
- 如果更新系统后问题仍未解决,按照微软官方支持文档(KB3118401)的指导手动安装 C Runtime 更新。
该错误的本质是新版 Python 依赖 UCRT(Universal C Runtime),而旧版 Windows 缺少对应的 api-ms-win-crt-* 动态 API 集组件。
十、相关仓库资源索引
| 主题 | 仓库路径 | 说明 |
|---|---|---|
| 本文对应文档 | Doc/faq/windows.rst | Windows FAQ 原文 |
| 独立二进制工具清单 | Doc/faq/programming.rst | faq-create-standalone-binary 小节 |
| freeze 打包工具 | Tools/freeze | 字节码转 C 数组、生成自包含二进制 |
| Windows 扩展模块构建 | Doc/extending/windows.rst | DLL、导入库与 PyInit_* 机制详解 |
| msvcrt 模块源码 | PC/msvcrtmodule.c | kbhit/getch 的 C 实现 |
| msvcrt API 文档 | Doc/library/msvcrt.rst | 完整函数列表与宽字符 API |
| .pyd 后缀定义 | PC/layout/support/builddetails.py | extension_suffix = ".pyd" |
| tabnanny 模块 | Lib/tabnanny.py | 批量检查缩进混用 |
| 包安装(Windows 前提) | Doc/installing/index.rst | 说明 Windows 下假定安装时勾选了"更新 PATH" |
适用前提与限制:本文内容以当前 CPython 仓库中的 Doc/faq/windows.rst 为准,面向使用标准 Windows 安装器的用户;部分历史细节(如 pythonNN.dll 的版本号命名、SWIG shadow class 流程)反映的是文档撰写时的最佳实践,实际项目中建议结合 Doc/extending/windows.rst 与当前版本的构建脚本核对最新的构建细节。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00