← 返回文章列表
POST / 2024-11-21

Python 报错:module 'collections' has no attribute 'MutableMapping'

解决 Python 3.10 及以上版本中 collections.MutableMapping 不存在的问题,并补充 Shadowsocks 在 OpenSSL 3 环境下的兼容处理。

运行旧版 Python 项目或依赖时,可能会遇到以下错误:

AttributeError: module 'collections' has no attribute 'MutableMapping'

问题原因

从 Python 3.3 开始,MutableMapping 等容器抽象基类被迁移到了 collections.abc。旧的兼容别名经过长期弃用后,在 Python 3.10 中从 collections 模块移除。

因此,下面这种旧写法在 Python 3.10 及以上版本中会报错:

from collections import MutableMapping

推荐解决方法

1. 修改自己的代码

将导入路径改为 collections.abc

from collections.abc import MutableMapping

如果代码还需要兼容较早的 Python 版本,可以使用:

try:
    from collections.abc import MutableMapping
except ImportError:
    from collections import MutableMapping

同类的 MappingSequenceIterableCallable 等抽象基类,也应从 collections.abc 导入。

2. 升级引发报错的第三方依赖

先根据异常堆栈找到仍在使用旧导入方式的包,再查看并升级它:

python --version
python -m pip show <package-name>
python -m pip install --upgrade <package-name>

如果最新版仍未修复,可以临时修改虚拟环境中对应包的源码,把:

from collections import MutableMapping

改为:

from collections.abc import MutableMapping

直接修改 site-packages 只适合临时排查,重新安装依赖后改动会丢失。长期方案应是升级依赖、提交补丁,或改用仍在维护的替代包。

不建议直接降级或删除系统 Python

安装 Python 3.9 可能暂时绕过报错,但并没有修复不兼容的导入方式。尤其不要执行删除系统 Python 的命令,否则依赖它的系统工具和软件包管理器可能无法正常工作。

如果旧项目暂时无法升级,建议使用 venvpyenv 或容器隔离所需的 Python 版本,不要替换操作系统自带的解释器。

Shadowsocks 的 OpenSSL 3 兼容问题

修复 MutableMapping 后,旧版 Shadowsocks 在使用 OpenSSL 3 时还可能出现另一个错误:

AttributeError: /lib/x86_64-linux-gnu/libcrypto.so.3:
undefined symbol: EVP_CIPHER_CTX_cleanup

这是因为旧代码仍在调用 EVP_CIPHER_CTX_cleanup。先确定实际加载的文件位置:

python -c "import shadowsocks.crypto.openssl as m; print(m.__file__)"

打开输出的 openssl.py,将原来的两行:

libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,)
ctx_cleanup = libcrypto.EVP_CIPHER_CTX_cleanup

替换为:

libcrypto.EVP_CIPHER_CTX_reset.argtypes = (c_void_p,)
ctx_cleanup = libcrypto.EVP_CIPHER_CTX_reset

更稳妥的兼容写法是同时支持新旧 OpenSSL:

try:
    libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,)
    ctx_cleanup = libcrypto.EVP_CIPHER_CTX_cleanup
except AttributeError:
    libcrypto.EVP_CIPHER_CTX_reset.argtypes = (c_void_p,)
    ctx_cleanup = libcrypto.EVP_CIPHER_CTX_reset

不要使用 :%s/cleanup/reset 进行全文替换:它可能同时改坏 ctx_cleanup 等变量名,却遗漏同一行后面的函数名。

参考资料