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
同类的 Mapping、Sequence、Iterable 和 Callable 等抽象基类,也应从 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 的命令,否则依赖它的系统工具和软件包管理器可能无法正常工作。
如果旧项目暂时无法升级,建议使用 venv、pyenv 或容器隔离所需的 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 等变量名,却遗漏同一行后面的函数名。