- 游戏开发
- 后端
【免费下载链接】kbengine
A MMOG engine of server.
本文以 KBEngine 仓库中随源码分发的 CPython 文档 platform.rst 为主体,完整讲解 Python 标准库platform模块的跨平台 API 设计:它如何从系统命令、注册表、环境变量等多条路径兜底地获取平台识别数据,以及uname()、architecture()、platform()等函数的实现细节与缓存机制。读完后你不仅能熟练查询系统/解释器信息,还能理解 KBEngine 跨平台安装工具是如何基于这套 API 做分支判断与环境诊断的。
一、背景:KBEngine 内置的 Python 与 platform 文档
KBEngine 是一款 MMO 游戏服务器引擎,服务端脚本层深度依赖嵌入式 Python。为了便于在不同操作系统上构建,仓库在 kbe/src/lib/python 下完整携带了一份 CPython 源码树(Lib/、Include/、Modules/、Doc/等目录齐全)。从 patchlevel.h 中可以看到:
#define PY_VERSION "3.7.3"即该内置解释器为 Python 3.7.3。platform模块正是这套解释器标准库的一部分:
- 文档:platform.rst
- 实现:platform.py(约 1430 行,纯 Python 实现)
文档开头的模块说明概括了它的设计目标:
Source code:
Lib/platform.py— Retrieves as much platform identifying data as possible.(获取尽可能多的平台识别数据)
同时文档给出两条使用约定,值得在实现层面注意:
- 各平台小节按字母序排列,Linux 归入 Unix 小节;
- 凡是无法确定的值一律返回空字符串
'',而不是抛异常。这一"保守返回"约定贯穿所有 API,是编写跨平台代码时最重要的心智模型。
另外注意区分:KBEngine 的 C++ 层有一个同名但完全不同的概念——pyscript/py_platform.h 中的PyPlatform类,它只负责脚本环境下的路径操作(rmdir、pathExists、splitPath等),与本文讨论的标准库platform模块无关。
二、跨平台通用 API(Cross Platform 小节)
文档的 "Cross Platform" 一节列出了与具体系统无关的函数,也是日常最常用的部分。
2.1 uname():可移植的 uname 接口
uname()返回一个collections.namedtuple,包含六个属性:system、node、release、version、machine、processor。文档特别指出两点与os.uname的差异:
- 比
os.uname()多了一个第六项processor; - 前两个属性命名不同:
os.uname叫sysname/nodename,这里叫system/node。
文档中的版本变更记录(.. versionchanged:: 3.3)说明:3.3 起返回值从普通 tuple 改为 namedtuple。这一点在源码中得到印证,platform.py 定义了结果类型:
uname_result = collections.namedtuple("uname_result", "system node release version machine processor")实现要点(源码级)。uname()的完整实现在 platform.py L923-L1052,它的取值链条是多层兜底:
- 首选
os.uname():直接调用 C 层uname系统调用,system, node, release, version, machine = os.uname(); - 无
os.uname或返回空时,回退到sys.platform取值,并按平台分别处理:win32平台:调用win32_ver()从注册表获取release/version;machine优先读PROCESSOR_ARCHITEW6432环境变量(因为 WOW64 进程会掩盖原生架构),其次PROCESSOR_ARCHITECTURE;processor读PROCESSOR_IDENTIFIER;- 仍失败时执行
ver系统命令(_syscmd_ver),并对 Windows Vista/Server 2008 起ver输出格式变化做了专门归一化('Microsoft Windows'→'Windows',6.0前缀 →'Vista'); java*平台则转入java_ver();
- OpenVMS 特殊处理:其
release与version语义颠倒需要交换,并通过vms_lib判断 CPU 是 Alpha 还是 VAX; - processor 仍为空时执行
uname -p系统命令; - 收尾归一:所有取值为
'unknown'的字段统一替换为''。
此外uname()使用模块级变量_uname_cache做全量缓存(L921),进程内首次调用后结果被固定,后续调用直接返回——这对性能友好,但也意味着运行期间无法反映动态变化。
2.2 单值查询函数族
文档将六个细粒度函数列在uname()周边,它们本质上都是从uname()结果中提取单个字段,取值失败时返回'':
| 函数 | 返回内容 | 文档给出的示例 |
|---|---|---|
machine() | 机器类型 | 'i386' |
node() | 计算机网络名(不保证是 FQDN) | — |
processor() | 真实处理器名 | 'amdk6' |
release() | 系统发行版 | '2.2.0'或'NT' |
system() | 系统/OS 名 | 'Linux'、'Windows'、'Java' |
version() | 系统版本详情 | '#3 on degas' |
文档对processor()有个值得记住的提醒:很多平台不提供该信息,或者干脆返回与machine()相同的值(文档点名 NetBSD 如此)。这正是platform()内部会做if machine == processor: processor = ''去重(见 2.5 节)的原因。
2.3 architecture():位宽与链接格式
architecture(executable=sys.executable, bits='', linkage='')查询给定可执行文件(默认是 Python 解释器二进制)的架构信息,返回字符串二元组(bits, linkage),如('64bit', 'ELF')。文档说明的核心机制与源码实现完全对应(platform.py L834-L914):
- bits 的默认推断:若未指定,用
struct.calcsize('P')(指针大小,老版本回退struct.calcsize('l'))乘以 8 得到'32bit'/'64bit'; - 依赖系统的
file命令:通过_syscmd_file()执行file -b并解析输出——'32-bit'/'64-bit'/'N32'(Irix 专有)决定位宽,'ELF'/'PE'/'COFF'/'MS-DOS'决定链接格式(Windows 的 PE 会被进一步标记为'WindowsPE'); - 无
file命令时回退到模块内的_default_architecture表(按sys.platform索引)提供合理默认值。
文档还专门用一个 note 提醒了一个经典坑:在 Mac OS X(及其他可能存在 universal binary 的平台)上,可执行文件可能同时包含多个架构,用architecture()判断"当前解释器是不是 64 位并不可靠"。更可靠的方式是直接查询sys.maxsize:
is_64bits = sys.maxsize > 2**322.4 system_alias():营销名归一
system_alias(system, release, version)将系统自报的名称改写为常见的"营销名称",并在信息顺序容易造成混淆时做重排。文档给出的例子是:SunOS 会被报告为 Solaris。该函数被platform(aliased=1)调用,是理解aliased参数的关键。
2.5 platform():"平台字符串总装线"
platform(aliased=0, terse=0)返回单个字符串,尽可能多地标识底层平台。文档明确了两点设计取向:
- 输出面向人类阅读(human readable),不是机器可解析格式,各平台输出风格不同是有意为之;
aliased为真时经system_alias()做别名替换;terse为真时只返回标识平台所需的最少信息。
源码(platform.py L1334-L1421)展示了它的分支结构,可以视为整个模块的"总装线":
- 先取
uname()六元组,去掉与machine重复的processor,按需做system_alias(); - Windows:追加
win32_ver()的 CSD(服务包)信息,terse时只输出system + release; - Linux:尝试
dist()获取发行版名与版本(并抑制弃用告警);拿不到发行版信息时退化到libc_ver()报告 libc 名称与版本,输出形如Linux-3.10.0-x86_64-with-CentOS-7.6-Alt这样的字符串; - Java:组合
java_ver()的 VM 与宿主 OS 信息; - MacOS:
system + release (+ machine); - 其他平台(通用路径):调用
architecture(sys.executable)把位宽与链接格式也拼进来。
结果按(aliased, terse)二元组缓存在_platform_cache中,同一参数组合只计算一次。
platform.py底部还提供了命令行入口(L1425-L1430):直接运行python platform.py会打印别名的冗长平台串,支持terse/--terse与nonaliased/--nonaliased两个开关。文件头注释说明其输出格式"可用作文件名的一部分",这正是它的典型用途——在日志、归档、CI 产物命名中标识运行环境。
2.6 Python 解释器信息六连
除平台信息外,文档还列出一组以python_前缀开头的函数,全部基于解释器自身的版本信息:
| 函数 | 返回 |
|---|---|
python_version() | 字符串'major.minor.patchlevel',始终包含 patchlevel(缺省为 0),与sys.version不同 |
python_version_tuple() | 字符串元组(major, minor, patchlevel) |
python_branch() | 实现所在 SCM 分支(CPython 即构建来源分支) |
python_revision() | 实现所在 SCM 修订号 |
python_build() | 元组(buildno, builddate) |
python_compiler() | 编译 Python 所用编译器 |
python_implementation() | 实现名称,可能取值为'CPython'、'IronPython'、'Jython'、'PyPy' |
源码上这些函数都是薄封装,统一委托给内部的_sys_version()并取不同索引位,例如python_implementation()返回_sys_version()[0](platform.py L1253-L1264),python_version_tuple()则是tuple(_sys_version()[1].split('.'))。
三、平台专属 API
3.1 Java 平台:java_ver()
java_ver(release='', vendor='', vminfo=('','',''), osinfo=('','',''))Jython 的版本接口。返回(release, vendor, vminfo, osinfo),其中vminfo = (vm_name, vm_release, vm_vendor)、osinfo = (os_name, os_version, os_arch);无法确定的项回落到调用方传入的默认值(默认全为'')。
3.2 Windows 平台:win32_ver() 与已废弃的 popen()
win32_ver(release='', version='', csd='', ptype='')从 Windows 注册表读取附加版本信息,返回(release, version, csd, ptype)。文档对ptype的语义解释很具体:
- 单处理器 NT 机器上是
'Uniprocessor Free',多处理器机器上是'Multiprocessor Free'; - 其中
'Free'表示 OS 版本不含调试代码;也可能显示'Checked',表示该 OS 版本带参数/范围检查等调试代码。
文档 note 还提到:该函数在装有 Mark Hammond 的 win32all 包时效果最佳,Python 2.3+(实际支持始于 2.6)可直接使用,且显然只能在 Win32 兼容平台上运行。
popen(cmd, mode='r', bufsize=-1)Win95/98 小节下的可移植popen接口:优先查找win32pipe.popen实现,因为在 Windows 9x 上由于 MS C 库的缺陷,普通方式会挂起。文档标注其自 3.3 起弃用,明确应改用subprocess模块,新代码不应再调用它。
3.3 Mac OS 平台:mac_ver()
mac_ver(release='', versioninfo=('','',''), machine='')返回(release, versioninfo, machine),versioninfo = (version, dev_stage, non_release_version)。无法确定的项为'',所有元素均为字符串。
3.4 Unix 平台:dist/linux_distribution(已移除)与 libc_ver()
linux_distribution(distname='', version='', id='', supported_dists=('SuSE','debian','redhat','mandrake',...), full_distribution_name=1)尝试确定 Linux 发行版名称。参数语义:
supported_dists:指定要探测的发行版集合(按发行版的 release 文件名识别),默认为当前受支持的发行版列表;full_distribution_name:为真(默认)时返回 OS 报告的完整发行版名,否则返回取自supported_dists的短名;- 返回值
(distname, version, id)取默认参数为回退值,其中id是版本号后括号中的部分,通常是版本代号。
文档以.. deprecated-removed:: 3.5 3.8标注:该函数 3.5 弃用、3.8 移除(dist()是它的别名,同样被移除)。需要发行版信息时应改用第三方distro包。这也解释了为何 2.5 节中platform()内部调用dist('')时要包一层warnings.catch_warnings()来压制DeprecationWarning——因为本仓库内置解释器是 3.7.3,函数尚在但已弃用。
libc_ver(executable=sys.executable, lib='', version='', chunksize=16384)探测可执行文件(默认 Python 解释器)所链接的 libc 版本,返回字符串元组(lib, version),探测失败时回落到参数默认值。文档坦承该函数"对不同 libc 版本如何向可执行文件添加符号有深入的了解,可能只对 gcc 编译的可执行文件可用",且文件按chunksize字节分块读取扫描。
四、KBEngine 中的实战用法
platform模块在 KBEngine 工具链中不是摆设,最直接的用例是跨平台安装工具 kbe/tools/server/install/installer.py。
1. 用platform.system()做操作系统分支。文件开头就根据系统名决定导入哪些平台专属模块(L12-L31):
if platform.system() == 'Windows': pass else: import pwd # Unix 专属模块 if platform.system() == 'Windows': import winreg # (Python3) / _winreg (Python2)这就是 2.2 节system()函数"返回'Windows'或'Linux'"约定最典型的消费方:用返回值做if分支判断,而不是解析sys.platform。全文中该判断出现了二十余处,覆盖注册表操作、路径分隔、服务安装等全部平台差异点。
2. 用platform.platform()输出环境诊断。安装流程会打印运行环境快照(L451-L455):
def echoSystemEnvironment(): OUT_MSG("platform=" + platform.platform()) OUT_MSG("python_version=" + sys.version) OUT_MSG("python_path=" + sys.executable) OUT_MSG("currpath=" + os.getcwd())platform.platform()一条命令即给出"系统-发行版-机器-处理器-发行版信息"的完整人类可读串,配合sys.version/sys.executable,正是文档所说"human readable"定位的典型场景:问题上报时让用户贴出这段输出,即可快速锁定平台差异。
3. 结合 2.3 节的 64 位判断。由于 KBEngine 引擎本身存在 32/64 位二进制差异,若需在脚本中可靠判断指针位宽,应遵循文档给出的sys.maxsize > 2**32方案,而非解析architecture()的输出。
五、使用要点与注意事项小结
- 空字符串约定:所有取不到值的字段统一为
'',调用方必须自行判空,不会收到异常; - 缓存语义:
uname()与platform()均带模块级缓存,同进程内结果固定,重复调用几乎零成本,但不要在依赖实时信息的场景反复假设它会刷新; platform()输出不可机器解析:需要结构化数据时用uname()的 namedtuple;- 弃用面:
popen()(3.3 弃用)、linux_distribution()/dist()(3.5 弃用、3.8 移除)在本仓库内置的 3.7.3 解释器中仍处于"可用但会告警/即将移除"状态,新代码应改用subprocess与第三方distro包; - 位宽判断优先用
sys.maxsize,architecture()的结果受file命令与 universal binary 影响; - 不要把 KBEngine 自有的 PyPlatform(C++ 路径工具类)与标准库
platform模块混淆。
综上,platform模块通过"os.uname优先 + 系统命令/注册表/环境变量多层兜底 + 保守空串回退"的架构,把"尽可能多获取平台信息"这一目标落实到了每个函数上;而 KBEngine 的安装工具链则展示了这套 API 在真实跨平台工程中的两种核心用法——分支判断(system())与环境诊断输出(platform())。
- 游戏开发
- 后端
【免费下载链接】kbengine
A MMOG engine of server.
相关推荐
KBEngine 内置 Python 3.7.3 数据类详解:dataclasses 模块从原理到实战
KBEngine 内置 Python 3.7.3 数据类详解:dataclasses 模块从原理到实战 KBEngine 服务端脚本引擎基于 Python 实现
游戏开发后端KBEngine 内置 CPython 3.7.3:C 扩展模块移植 Python 3 的 C-API 实践指南
KBEngine 内置 CPython 3.7.3:C 扩展模块移植 Python 3 的 C API 实践指南 本篇基于 KBEngine 仓库内置的 CPy
游戏开发后端KBEngine 内置 Python 的 lzma 模块详解:LZMA 压缩 API 全指南
KBEngine 内置 Python 的 lzma 模块详解:LZMA 压缩 API 全指南 导读:本文基于 KBEngine 仓库内置 Python 发行版中
游戏开发后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考