17370845950

如何检测当前 Python 是否运行在 Nuitka 编译的可执行文件中
最直接可靠的方式是检查 __compiled__ 全局变量是否存在且为 True,Nuitka 编译时注入该变量,值恒为 True,所有目标平台均支持,且其他 Python 环境和打包工具(如 PyInstaller、cx_Freeze)均未定义。

判断 sys.executable 是否指向 Nuitka 生成的二进制文件

最直接可靠的方式是检查 sys.executable 路径是否为一个真实存在的、非 Python 解释器的可执行文件。Nuitka 编译后,sys.executable 指向最终打包出的二进制(如 myapp.exe./myapp),而非 pythonpython3

注意:不能只靠文件名判断(比如含 “nuitka” 字样),因为用户可重命名;也不能依赖 sys.version 等字段,Nuitka 会模拟 CPython 版本字符串。

  • 检查 os.path.isfile(sys.executable)os.access(sys.executable, os.X_OK)
  • shutil.which("python")shutil.which("python3") 对比 —— 若 sys.executable 不等于其中任一结果,大概率是 Nuitka 产物
  • Windows 下可额外检查 sys.executable.endswith(".exe") and not sys.executable.lower().endswith(("python.exe", "py.exe"))

读取 Nuitka 运行时标记 __compiled__ 全局变量

Nuitka 在编译时会注入一个内置全局变量 __compiled__,值为 True。这是官方支持的检测方式,轻量且无副作用。

它在所有 Nuitka 编译目标(包括 Windows .exe、Linux/macOS 可执行文件、以及使用 --standalone--onefile)中都存在,且未被任何常见 Python 环境(CPython、PyPy、conda、venv)设置过。

  • 直接写 if "__compiled__" in globals() and __compiled__: 即可安全判断
  • 不要用 try/except NameError 包裹访问 __compiled__ —— 它是全局变量,不是内置函数,不存在调用开销
  • 该变量在源码直跑时完全不存在,不会误判;也无需导入任何模块

避免误判:区分 PyInstaller、cx_Freeze 等其他打包工具

很多开发者会混淆 Nuitka 和 PyInstaller 的运行时特征。PyInstaller 设置的是 getattr(sys, 'frozen', False),而 cx_Freeze 用 sys.frozen == True。这些和 __compiled__ 互不干扰,但混用逻辑会导致错误。

  • 不要把 sys.frozen 当作 Nuitka 标志 —— Nuitka 默认不设它(除非显式加 --enable-plugin=pyinstaller
  • 不要检查 sys._MEIPASS —— 这是 PyInstaller 私有路径,Nuitka 不创建该属性
  • 若需兼容多种打包器,应分别判断:__compiled__(Nuitka)、getattr(sys, "frozen", False)(PyInstaller/cx_Freeze)、hasattr(sys, "_MEIPASS")(PyInstaller)

为什么不用 sys.argv[0] 或进程名检测?

sys.argv[0] 不可靠:它可能被脚本修改、可能只是相对路径、也可能在某些 one-file 启动场景下回退为解释器路径;进程名(如 psutil.Process().name())需要额外依赖,且在容器或受限环境里权限不足。

更关键的是,这些方式无法区分“Nuitka 编译后运行”和“用 Nuitka 编译的程序又被其他 launcher 调起”的情况 —— 而 __compiled__ 是编译期写死的 Python 层标识,语义明确。

真正容易被忽略的点

是:如果你用了 Nuitka 的 --include-package 或插件机制动态加载模块,那些模块内部仍能访问到 __compiled__ —— 它是全局生效的,不是仅限于主模块。