首页
/ CPython `platform` 模块完全指南:跨平台识别操作系统、架构与运行环境

CPython `platform` 模块完全指南:跨平台识别操作系统、架构与运行环境

2026-09-07 09:35:43作者:柏廷章Berta

本篇技术指南以 CPython 标准库文档 Doc/library/platform.rst 为骨架,结合该模块的标准库实现 Lib/platform.py 与回归测试 Lib/test/test_platform.py,系统讲解 Python platform 模块的完整 API、底层数据来源与典型应用场景。读完本文,你将掌握如何可靠地探测操作系统名称与版本、CPU 架构与字节位宽、libc 类型、当前 Python 解释器的构建信息,并能在打包、安装脚本、调试工具等真实场景中正确选用这些 API。

platform 是 Python 标准库中负责"尽可能多地采集底层平台标识数据"的模块(模块 docstring 原文见 Lib/platform.py)。它把不同操作系统上来源各异的底层接口(os.unamever 命令、Windows 注册表、WMI、macOS plist、Android system_property、Linux os-release 文件等)统一收敛为一组简单、可移植、带合理默认值的 Python 函数,并被 sysconfig、打包工具、构建系统与各类安装脚本广泛依赖。

模块设计的总原则

在深入每个 API 之前,先理解三个贯穿全模块的设计约定,这有助于你正确解读返回值、避免写出脆弱代码:

  1. 取不到就回退到默认参数,而不是抛异常。 几乎所有平台查询函数(如 architecture()win32_ver()ios_ver()android_ver())都允许调用方传入"兜底值"(默认多为空字符串或 False)。当某项信息无法确定时,函数返回你传入的兜底值,保证调用流程不被中断。
  2. "未知"统一表示为空字符串。 例如 uname() 的源码在返回前会把底层报告为 'unknown' 的值统一替换成 ''(对应 Lib/platform.py_unknown_as_blankLib/platform.py 的缓存构造逻辑),这是与 os.uname 的一个显著差异。
  3. 结果会被缓存,且可显式失效。 uname()platform()freedesktop_os_release()_sys_version() 的结果都有模块级缓存(如 _uname_cache_platform_cache_os_release_cache)。当外部进程修改了机器名等环境信息后,可用 invalidate_caches() 清空全部缓存后重新查询。该函数于 Python 3.14 新增,详见文档中的 .. versionadded:: 3.14 标注;其源码实现同时重置了四类缓存(Lib/platform.py)。

跨平台通用函数

文档按平台将 API 分组,第一组是"Cross platform"通用函数,可在绝大多数平台上直接调用。

获取解释器架构信息:architecture()

architecture(executable=sys.executable, bits='', linkage='') 查询指定可执行文件(默认是当前 Python 解释器二进制)的架构信息,返回形如 (bits, linkage) 的元组,两个元素均为字符串。

理解该函数的关键在于其实现机制(Lib/platform.py):

  • 它依赖系统 file 命令做实际探测,通过子进程执行 file -b <target>,且会先把 LC_ALL 强制设为 C 以保证输出确定可解析(见 _syscmd_file 实现 Lib/platform.py);
  • bits 为空字符串,则用 struct.calcsize('P')(即指针字节数 × 8)作为默认位数,例如 64 位机器得到 '64bit'
  • file 命令不可用的平台上(如 Windows、DOS),若目标正是解释器本体,会使用源码顶部的 _default_architecture 表给出合理默认,例如 win32 平台对应 ('', 'WindowsPE')Lib/platform.py);
  • 链接格式(linkage)通过识别 file 输出中的关键字得到:ELFMach-OPE/WindowsPECOFFMSDOS 等。
import platform

print(platform.architecture())  # 例如 ('64bit', 'ELF') 或 ('64bit', 'WindowsPE')

注意(文档原注):在 macOS(及可能的其他平台)上,可执行文件可能是包含多种架构的"通用二进制"(universal file)。若只是想知道当前解释器是否为 64 位,更可靠的做法是查询 sys.maxsize

import sys

is_64bits = sys.maxsize > 2**32

机器型号、网络名与处理器:machine()node()processor()

  • machine():返回机器类型,文档示例为 'AMD64'。输出与平台有关,大小写与命名约定可能各不相同;无法确定时返回空字符串。
  • node():返回计算机的网络名(可能不是完整限定域名),无法确定时返回空字符串。该值底层来自 socket.gethostname()(见 _node 实现 Lib/platform.py)。
  • processor():返回"真实"的处理器名称(如 'amdk6')。文档特别提醒:许多平台并不提供该信息,或者直接返回与 machine() 相同的值(NetBSD 即是如此)。源码中,processor 字段属于"延迟解析"字段——只有真正访问它时才去执行 uname -p 之类的探测,详见下文 uname() 小节。

这三个函数实际都是 uname() 返回的具名元组对应字段的便捷封装(见 Lib/platform.py),因此也共享其缓存。

单字符串平台标识:platform()system_alias()

platform(aliased=False, terse=False) 把尽可能多的有用信息拼成一个字符串返回。文档明确说明:输出面向人类阅读而非机器解析,在不同平台可能呈现不同格式,这是刻意设计。

import platform

print(platform.platform())            # 例如 Linux-6.1.0-x86_64-with-glibc2.36
print(platform.platform(aliased=True, terse=True))  # 例如 Linux-6.1.0-x86_64

两个布尔参数的含义:

  • aliased=True:当系统自报名称与其通用营销名称不同时使用别名,例如把 SunOS 报告为 Solaris。该功能由 system_alias(system, release, version) 函数实现。
  • terse=True:只返回标识平台所需的最小信息。

platform() 内部的组装逻辑(Lib/platform.py)展示了对不同平台的分支处理:Darwin 系会被进一步识别为 iOS 或改写为 macOS(macOS 版本取自 mac_ver());Windows 使用 win32_ver() 补充 CSD/补丁信息;Linux 会调用 libc_ver() 并把 libc 名字与版本号以 with 为分隔拼进字符串(如 ...-with-glibc2.36);其他平台则追加 architecture() 得到的 bitslinkage。组装前还会经过 _platform() 内部助手把输出清洗成"可用作文件名"的形态(空格、/: 等被替换,unknown 字样被剔除,见 Lib/platform.py)。

版本行为变更(.. versionchanged:: 3.8:从 Python 3.8 起,在 macOS 上 platform() 会优先使用 mac_ver() 返回的非空 release 字符串来获取 macOS 版本,而不再使用 darwin 内核版本。

Python 解释器自身信息:python_* 系列函数

这一族函数从 sys.version 解析出解释器自身的构建与版本信息,底层由 _sys_version() 统一解析并缓存(Lib/platform.py)。文档列出的 API 与返回内容如下:

函数 返回内容 备注
python_build() (buildno, builddate) 元组 构建号与构建日期,均为字符串
python_compiler() 编译 Python 所用的编译器标识字符串 GCC 12.2.0
python_branch() Python 实现的 SCM 分支名 sys._git/sys._mercurial 读取
python_revision() Python 实现的 SCM 修订号 同上
python_implementation() Python 实现名称 可能值为 'CPython''IronPython''Jython''PyPy'
python_version() 字符串版本 'major.minor.patchlevel' 见下
python_version_tuple() 元组版本 (major, minor, patchlevel) 元素为字符串

文档特别强调的一个易错点:与 sys.version 不同,python_version() / python_version_tuple() 总会包含 patchlevel,缺省时补 0(解析器在 Lib/platform.py 中为两段式版本号自动追加 .0)。也就是说即便安装的是 3.11,该函数也会返回 '3.11.0' 而非 '3.11'

实现层面,CPython、Jython、PyPy 各自有不同的 sys.version 正则解析器(Lib/platform.py);CPython 的正则还兼容了 free-threading build(自由线程构建)标签,这对应 3.13+ 的无 GIL 实验性构建。

系统标识快速入口:release()system()version()uname()

  • release():系统发布版本,如 '2.2.0''NT'
  • system():系统/OS 名称,如 'Linux''Darwin''Java''Windows'
  • version():系统发布版本的详细字符串,如 '#3 on degas'

uname() 是这一组的核心:它提供一个"相当可移植"的 uname 接口,返回一个具名元组namedtuple),包含六个属性:systemnodereleaseversionmachineprocessor

import platform

u = platform.uname()
print(u.system)      # 例如 Linux
print(u.node)        # 例如 myhost
print(u.machine)     # 例如 x86_64
print(u.processor)   # 可能为空字符串

三点文档要点,理解 uname() 时很关键:

  1. 前两个属性名与 os.uname 不同os.uname 中对应字段叫 sysnamenodename,而 platform.uname 用的是 systemnode,两者不可混用。
  2. processor 是延迟解析的:该字段通过 uname_result.processor 上的 functools.cached_property 实现——只有真正访问 processor 时才调用 _Processor.get()(其默认分支会执行 uname -p 子进程探测,Windows 上则查询 WMI 或 PROCESSOR_IDENTIFIER 环境变量),且解析结果会缓存。这一行为是 Python 3.9 变更的(.. versionchanged:: 3.9),此前该字段在创建结果时即被立即填充。
  3. 无法确定的条目一律为 '':源码用 _unknown_as_blank'unknown' 归一为空串(Lib/platform.py)。

历史上,uname() 的返回类型在 Python 3.3 从普通元组变更为具名元组(.. versionchanged:: 3.3)。由于是具名元组,platform.uname() 结果还支持 _asdict()、按位置/切片访问、copypickle 等操作——这些特性都有对应测试覆盖(见 Lib/test/test_platform.py)。对返回值与底层 os.uname 各字段一致性的验证,见测试中的 test_unameLib/test/test_platform.py)。

iOS / Android 上的语义差异(3.13+ 平台支持):在 iOS 与 Android 上,system() / release() 返回的是用户可见的 OS 名称与版本——system() 可能是 'iOS''iPadOS''Android'release() 是用户面对的 OS release;若要获取 Darwin 或 Linux 内核名('Darwin'/'Linux')与内核 release,应改用 os.uname。这正是 uname() 源码中针对 sys.platform == 'android''ios' 的专门归一化逻辑(Lib/platform.py)。

缓存失效:invalidate_caches()

invalidate_caches()(Python 3.14 新增)清空模块内部信息缓存,如 uname() 的结果。文档给出的典型用途是:当平台 node(机器名)被外部进程修改、且你需要拿到更新后的值时,先调用它再重新查询。其实现会同时重置 _uname_cache_os_release_cache_sys_version_cache_platform_cacheLib/platform.py)。测试中的 test_invalidate_caches 也验证了调用后各缓存确实被置空(Lib/test/test_platform.py)。

Windows 平台:win32_ver()win32_edition()win32_is_iot()

获取版本四元组:win32_ver()

win32_ver(release='', version='', csd='', ptype='') 返回 (release, version, csd, ptype) 四元组,分别对应:OS release(如 '7''10''11')、版本号、CSD 级别(Service Pack)、OS 类型(单/多处理器)。无法确定的值取传入的默认参数(默认为空串)。

csdptype 的含义文档有专门提示:

  • ptype 在单处理器 NT 机器上为 'Uniprocessor Free',多处理器机器为 'Multiprocessor Free'。这里的 'Free' 表示该 OS 版本不含调试代码;也可能出现 'Checked',表示使用了带调试代码的版本(即会检查参数、范围等的代码)。
  • 实际实现中 ptype 对应 Windows 注册表键 SOFTWARE\Microsoft\Windows NT\CurrentVersion 下的 CurrentType 值,参见 Lib/platform.py

源码揭示了比文档更丰富的底层探测链(Lib/platform.py):

  1. 优先走 WMIWin32_OperatingSystem 查询,见 _wmi_query,可获取 VersionProductTypeBuildType、ServicePack 号)——注释称其为"canonical source of data"(权威数据源),失败才降级;
  2. 降级到 sys.getwindowsversion()ver 命令的组合;
  3. release 的营销名(如把 10.0.22000 报告为 11)由两个内置表映射:客户端 _WIN32_CLIENT_RELEASES(含 Vista781011post11)与服务器 _WIN32_SERVER_RELEASES(含 2008ServerR22019Server2022Server2025Server 等),见 Lib/platform.py

非 Windows 平台上调用该函数也不会失败,而是返回传入的兜底值,对应测试见 Lib/test/test_platform.py

版本与版本类型:win32_edition()win32_is_iot()

两者均于 Python 3.8 新增:

  • win32_edition():返回当前 Windows 版本(edition)字符串,无法确定时返回 None。可能值包括但不限于 'Enterprise''IoTUAP''ServerStandard''nanoserver'。实现直接读取注册表 EditionId 值(Lib/platform.py)。
  • win32_is_iot():当 win32_edition() 的返回值属于被认可的 IoT 版本集合时返回 True。判定集合在源码中为 ('IoTUAP', 'NanoServer', 'WindowsCoreHeadless', 'IoTEdgeOS')Lib/platform.py)。

macOS 平台:mac_ver()

mac_ver(release='', versioninfo=('', '', ''), machine='') 获取 macOS 版本信息,返回 (release, versioninfo, machine) 三元组,其中 versioninfo 本身是 (version, dev_stage, non_release_version) 三元组;所有元组条目均为字符串,无法确定时取参数默认值(多为 '')。

实现细节(Lib/platform.py):该函数并不执行 shell 命令,而是读取 /System/Library/CoreServices/SystemVersion.plist(macOS 上几乎总是存在),用 plistlib 解析出 ProductVersion 作为 release;machine 取自 os.uname().machine,且把 'ppc'/'Power Macintosh' 规范化为 'PowerPC'。文档与源码注释均指出,versioninfo 中携带开发阶段信息(如 beta)的机制在较新 macOS 上已不再提供(测试中校验其恒为 ('', '', ''),见 Lib/test/test_platform.py)。测试还验证了 Darwin 上返回的机器名属于 i386/x86_64/arm64/PowerPC 集合,以及该函数在 fork 场景下的可用性(issue7895 回归测试,见 Lib/test/test_platform.py)。

iOS 平台:ios_ver()

ios_ver(system='', release='', model='', is_simulator=False)(iOS 支持于较新的 3.13 系列版本引入,但该函数本身按需工作)返回一个具名元组,字段如下:

属性 含义
system OS 名称,'iOS''iPadOS'
release iOS 版本号字符串,如 '17.2'
model 设备型号标识:真机形如 'iPhone13,2',模拟器则为 'iPhone'
is_simulator 布尔值,是否运行在模拟器上

无法确定的条目取参数默认值。源码中,只有在 sys.platform == "ios" 时才尝试从内部 _ios_support 扩展读取真实数据,其他平台直接返回默认值构造的具名元组,因此在非 iOS 平台调用不会报错Lib/platform.py)。该行为与测试吻合:非 iOS 平台断言返回全部默认值、且可用参数覆盖(见 Lib/test/test_platform.py)。

Unix 平台:libc_ver()

libc_ver(executable=sys.executable, lib='', version='', chunksize=16384) 尝试判定 executable(默认是 Python 解释器)所链接的 libc 版本,返回 (lib, version) 字符串元组;查找失败时返回传入的默认参数。

两点文档提示值得特别留意:

  1. 该函数对"不同 libc 版本如何向可执行文件添加符号"有非常深入的内部知识,因此很可能只对用 gcc 编译的可执行文件有效;
  2. 文件被按 chunksize 字节(默认 16384)分块读取扫描,避免一次性载入大文件。

实现用一条覆盖 glibc 与 musl 的大正则(含 __libc_initGLIBC_x.y.zlibc.so.*musl-x.y.zlibc.musl*.so 等分支)在二进制内容中匹配符号(Lib/platform.py),并通过 _comparable_version 对版本号做语义化比较以挑出最大版本。额外的两条实用分支:

  • 未传 executable 时,在 Emscripten 平台直接返回 ('emscripten', emscripten_version),因为 Emscripten 的 os.confstr 会误报为 glibc(对应注释见 Lib/platform.py,测试见 Lib/test/test_platform.py);
  • 否则优先尝试 os.confstr('CS_GNU_LIBC_VERSION')(在 glibc 系统上直接给出 glibc 2.36 之类结果,一行解析即可),失败后才落到二进制扫描路径。

Linux 平台:freedesktop_os_release()

freedesktop_os_release()(Python 3.10 新增)从 os-release 文件读取操作系统标识并返回字典。该文件遵循 freedesktop.org 的 os-release 规范,存在于绝大多数 Linux 发行版;明显的例外是 Android 及 Android 衍生发行版

行为要点:

  • /etc/os-release/usr/lib/os-release 都无法读取时,抛出 OSError 或其子类(/etc 优先级更高,候选路径定义见 Lib/platform.py);
  • 成功时返回键值均为字符串的字典;值中的特殊字符(如 "$)已被去除转义(源码用 \\([\\\$\"\'])` 正则完成五类转义字符的还原,见 Lib/platform.py);
  • 标准要求 NAMEIDPRETTY_NAME 三个字段始终存在(解析器为缺省情况预置了 Linux/linux 默认值,见 Lib/platform.py),其余字段可选,厂商可自行增加字段。

字段选用建议(文档原文强调):像 NAMEVERSIONVARIANT 这类字段是适合直接展示给用户的字符串;而程序要做发行版识别时应使用 IDID_LIKEVERSION_IDVARIANT_ID。文档给出如下"识别衍生发行版"的惯用法(ID_LIKE 的值以空格分隔、按优先级排列):

import platform

def get_like_distro():
    info = platform.freedesktop_os_release()
    ids = [info["ID"]]
    if "ID_LIKE" in info:
        # ids are space separated and ordered by precedence
        ids.extend(info["ID_LIKE"].split())
    return ids

例如在 Ubuntu 上,IDubuntuID_LIKEdebian,该函数会返回 ['ubuntu', 'debian'],从而能识别出它是 Debian 系的衍生发行版。对解析、缺文件抛错与返回副本行为的测试见 Lib/test/test_platform.py;注意返回的是内部缓存字典的 copy(),防止调用方意外污染缓存(Lib/platform.py)。

Android 平台:android_ver()

android_ver(release="", api_level=0, manufacturer="", model="", device="", is_emulator=False)(Python 3.13 新增)获取 Android 设备信息,返回具名元组。无法确定的字段取参数默认值。字段含义如下:

属性 含义
release Android 版本字符串,如 "14"
api_level 运行设备的 API level 整数,如 Android 14 对应 34;若想获取 Python 所针对构建的 API level,请用 sys.getandroidapilevel()
manufacturer 厂商名(对应 Android Build.MANUFACTURER
model 型号名(对应 Build.MODEL),通常为营销名或型号编号
device 设备名(对应 Build.DEVICE),通常为型号编号或代号
is_emulator 是否运行在模拟器上

实现只会在 sys.platform == "android" 时真正探测(Lib/platform.py):通过 ctypes 绑定 libc 的 __system_property_get(NDK 官方支持的 API),依次读取 ro.build.version.releasero.build.version.sdkro.product.manufacturerro.product.modelro.product.devicero.kernel.qemu(值为 "1" 即判定为模拟器)等系统属性。因此在其他平台调用会直接返回默认值构造的元组

命令行用法:python -m platform

platform 模块自带命令行入口(源码见 Lib/platform.py_parse_args/_main),可通过解释器的 -m 开关直接调用:

python -m platform [--terse] [--nonaliased] [{nonaliased,terse} ...]

可用选项:

选项 / 位置参数 含义
--terse 只打印最精简的平台信息,等价于以 terse=True 调用 platform.platform()
--nonaliased 打印平台信息时不进行系统/OS 名称别名替换(如保留 SunOS 而不替换成 Solaris)
terse(位置参数) --terse 作用相同
nonaliased(位置参数) --nonaliased 作用相同

两种写法可以混用。_main() 中把二者归一后等价于调用 platform(aliased, terse) 并把结果打印到 stdout(Lib/platform.py)。实际行为细节:命令行默认采用别名模式(aliased=True),遇到 nonaliased(无论选项还是位置参数)即关闭别名;terse 同理取"或"关系。因此:

$ python -m platform
Linux-6.1.0-17-amd64-x86_64-with-glibc2.36

$ python -m platform --terse
Linux-6.1.0-17-amd64

$ python -m platform terse nonaliased
Linux-6.1.0-17-amd64

由于输出被清洗成"可用作文件名"的形态(见 _platform 对特殊字符的替换 Lib/platform.py),命令行结果常被脚本用来拼装平台相关的缓存目录名或安装包后缀。

从源码与测试看可靠性设计

读完文档再对照实现,能看到该模块刻意遵循了几条可靠性原则,这些原则值得在编写你自己的环境探测代码时借鉴:

  • 探测失败不扩散:每个探测源(file 命令、ver 命令、os.uname、WMI、注册表、plist、os.confstr)都有独立的异常兜底,单点失败后静默降级到下一候选或默认值。
  • 权威源优先、逐级降级:Windows 版本信息先试 WMI,再降级到 sys.getwindowsversion() + ver + 注册表;macOS 优先 plist;Android 有专门属性通道。测试中甚至有针对"无 WMI 环境"的降级路径覆盖(Lib/test/test_platform.py)。
  • 结果归一化'unknown'''、系统名统一(Microsoft WindowsWindows)、版本号补零、release/version 去掉末尾点号等,都由内部助手完成(如 _norm_versionLib/platform.py)。
  • 结果缓存 + 显式失效:多次调用零开销,环境变化时由 invalidate_caches() 兜底。

实战组合:一个环境指纹采集片段

把上述 API 组合起来,即可在不依赖任何第三方库的情况下输出一份接近完整的"环境指纹",适用于安装脚本打印诊断信息、记录 CI 日志或生成平台相关缓存键:

import platform
import sys

info = {
    # Python 解释器自身
    "implementation": platform.python_implementation(),   # CPython / PyPy / ...
    "python_version": platform.python_version(),           # 恒含 patchlevel,如 3.13.0
    "compiler": platform.python_compiler(),                # 如 GCC 12.2.0
    "branch": platform.python_branch(),                    # 发行版通常为空
    "revision": platform.python_revision(),
    "build": platform.python_build(),                      # (buildno, builddate)
    # 操作系统与硬件
    "system": platform.system(),                           # Linux / Darwin / Windows / iOS / Android / Java
    "release": platform.release(),                         # 用户可见 OS 版本(iOS/Android 上尤为关键)
    "node": platform.node(),                               # 本机网络名
    "machine": platform.machine(),                         # 如 AMD64 / x86_64
    "processor": platform.processor(),                     # 可能为空串
    "architecture": platform.architecture(),               # (bits, linkage),如 ('64bit', 'ELF')
    # 可读的单行摘要
    "platform": platform.platform(aliased=False, terse=False),
}

# Linux 下补充发行版识别;注意 Android 无 os-release
if sys.platform.startswith("linux") and not sys.platform.startswith("android"):
    info["os_release"] = platform.freedesktop_os_release()  # 含 NAME/ID/PRETTY_NAME 等

print(info)

platform 模块由于具备跨平台统一、失败优雅降级、结果稳定可缓存三大特性,已经成为 CPython 生态中"查询我在哪台机器、哪种系统、什么架构上运行"的标准答案。本文覆盖的 官方文档实现源码回归测试 都位于本仓库内,需要进一步确认某个返回值在特定平台的确切形态时,可直接查阅这三处。

登录后查看全文
热门项目推荐
相关项目推荐