DEBUG.WIKI
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS

setuptools 84 的 Extension 不能放进 set,构建清单怎样改

setuptools 84 的 Extension 改变了相等性与哈希行为,集合或字典清单可能在编译前失败。区分顺序收集与身份去重,避免破坏哈希约定。

升级 setuptools 84.0.0 后,构建脚本在把扩展放进集合时退出,报 TypeError: unhashable type: 'Extension',也可能显示扩展子类的名称。先检查 Python 侧的构建清单,不要先换编译器或增补头文件。

该版本引入的 dataclass 处理改变了 Extension 的相等性与哈希行为。默认生成相等比较后,__hash__ 为 None,因此不能再直接作为集合元素或字典键。正式 84.2.1 仍保留这条 dataclass 路径;没有证据表明仅升级到它就能恢复旧行为。

只是保存扩展顺序,可以用列表

setup(..., ext_modules=...) 本来就可以接收扩展列表。若集合仅被用作收集容器,并没有依赖无序去重,可以保留原来的扩展对象并改用列表。

但不要把 if extension not in items 当成恢复旧版“同一对象去重”的办法。dataclass 的相等比较可能让两个不同对象因字段相同而被判等,语义已经改变;含可变字段时,也不适合随意生成一个哈希来塞回 set。

原逻辑按对象身份登记时,显式使用身份键

构建流程确实需要按对象身份记录“哪个扩展被排除、原因是什么”时,可以让进程内的注册表使用 id(),并把对象本身保存在值里:

excluded = {}


def remember_exclusion(extension, reason):
    excluded[id(extension)] = (extension, reason)

这是一个构建进程内的示例。保留对象引用可以让登记期间该对象继续存活;id() 不能写入跨进程缓存或发行元数据,也不能作为持久化标识。如果同一扩展可能有多个排除原因,值应按原业务规则保存全部原因,而不是被这个单值示例覆盖。

原逻辑本来按扩展名称去重时,可以使用明确的名称键,但应先确认同名扩展是否被视为冲突。把身份去重悄悄改成名称去重,可能合并原来不同的构建配置。

不要直接给 Extension 赋 object.__hash__ 却保留按字段比较的 __eq__。两个相等对象若产生不同哈希,会破坏集合和字典的约定。全局 monkeypatch 或给数据类打开 unsafe_hash 也没有解决可变字段和相等性设计问题。

暂时无法修改第三方构建脚本时,可在依赖允许范围内恢复此前验证过的构建后端组合;约束应传入实际隔离构建环境,不只是外部运行环境。不要把构建清单错误当成必须关闭全部构建隔离的理由。

验收检查扩展数量、顺序、排除记录,以及两个字段相同但身份不同的扩展对象如何处理。随后再走项目原有的编译和导入验收;清单阶段不再报 TypeError,只证明对象收集恢复,不能证明生成的二进制已兼容目标平台。