如何确定地抑制 Python 中的 DeprecationWarning?

How to assuredly suppress a DeprecationWarning in Python?

我相信这个问题已经被提出了很多次,但我有一个特定的用例,我无法使用网络上描述的许多方法解决问题。

在我的一个项目中,我正在使用 joblib 库,它显示 DeprecationWarning 因为它在内部某处使用了 imp 库:

from sklearn.externals.joblib import Parallel, delayed

def main():
    xs = Parallel()(delayed(lambda x: x**2)(i) for i in range(1, 6))
    print(sum(xs))

if __name__ == '__main__':
    main()

我正在尝试使用解释器选项过滤掉警告 -W 但它没有帮助:

$ python -W ignore example.py                                                                                                                   
[...]/lib/python3.7/site-packages/sklearn/externals/joblib/externals/cloudpickle/cloudpickle.py:47:
DeprecationWarning: the imp module is deprecated in favour of importlib; 
see the module's documentation for alternative uses import imp
55

此外,我正在尝试使用 warnings 模块进行显式过滤,但它也无济于事:

import warnings
warnings.simplefilter('ignore', category=DeprecationWarning)
from sklearn.externals.joblib import Parallel, delayed

def main():
    xs = Parallel()(delayed(lambda x: x**2)(i) for i in range(1, 6))
    print(sum(xs))

if __name__ == '__main__':
    main()

我在 matplotlib 模块和其他一些第三方库中遇到了类似的问题。可能还有其他一些方法(即环境变量),但我不明白为什么这些解决方案不起作用。

谁能解释一下警告系统在 Python 中的实际工作原理?第三方库有可能故意覆盖客户端的警告设置吗?我会说这个问题对我来说是最晦涩的话题之一。

有趣的是,即使按照@Alex 的建议,我仍然有警告输出,如下所示:

import warnings
with warnings.catch_warnings():
    warnings.simplefilter('ignore', category=DeprecationWarning)
    from sklearn.externals.joblib import Parallel, delayed

def main():
    xs = Parallel()(delayed(lambda x: x**2)(i) for i in range(1, 6))
    print(sum(xs))

if __name__ == '__main__':
    main()

# $ python -W ignore example.py
# [...]
# DeprecationWarning: the imp module is deprecated in favour of importlib; 
# see the module's documentation for alternative uses
#  import imp
# 55

所以最终,我决定以一种非常 hacky 的方式来做到这一点并禁用所有警告,因为我有点厌倦了寻找正确的方法来处理它们。 (不仅对于这个库,而且对于许多其他似乎非常渴望用 non-suppressible 警告轰炸您的库)。

import warnings
def noop(*args, **kargs): pass
warnings.warn = noop
from sklearn.externals.joblib import Parallel, delayed

def main():
    xs = Parallel()(delayed(lambda x: x**2)(i) for i in range(1, 6))
    print(sum(xs))

if __name__ == '__main__':
    main()

如果我错误地使用了@Alex 的建议或者你们中的一些人有更好的解决方案,我很乐意接受它作为答案。


更新 1

好的,似乎很难影响包内部某处发出的警告。因此,最简单的方法可能是将 warnings.warn 替换为 noop,或者以某种方式提前导入内部依赖项并使用上下文管理器抑制它们。


更新 2

前段时间我发现了另一种可能的处理警告的方法。您可以将它们重定向到日志记录中。如果没有显式配置记录器,这些警告基本上会被抑制。它适用于 Jupyter 和我测试过的一些库。

import logging
logging.captureWarnings(True)

根据要求,这里是一个单独的答案post:

诀窍是在导入 sklearn 时使用 "with" 警告(或使用 sklearn 的依赖项,在我的例子中是 hdbscan 包):

with warnings.catch_warnings():
    # filter sklearn\externals\joblib\parallel.py:268:
    # DeprecationWarning: check_pickle is deprecated
    warnings.simplefilter("ignore", category=DeprecationWarning)
    import hdbscan

这将仅针对此模块禁用 DeprecationWarning(因为 warnings-修改附加到 with-block)。

请务必将此语句放在导入模块的代码中的第一个位置,否则将无法运行。例如。如果我在 __init__.py 中加载 hdbscan,并且上面的代码块出现在一些 sub-class 中,它也加载 hdbscan,我仍然会收到 DeprecationWarning,因为 Python如果 module/package 已经加载,则忽略任何后续的 import 语句。

因此,重要的是检查哪些 modules/packages 使用 joblib\parallel.py 以及那些来自线性 code-perspective 的最早加载到 python 对象堆的位置。

[编辑]

正如@devforfu 在评论中指出的那样,上述解决方案(不再)有效。自从 Python 3.7 DeprecationWarning is once again shown by default when triggered directly by code in __main__. 以来,我再次对此进行了调查。此外,ignore 当依赖项显式加载其他包的折旧模块时,警告似乎不起作用。

这就是我的 hdbscan 示例中发生的情况,该示例加载了折旧模块 sklearn.external.sixsklearn.externals.joblib

以下是最终解决这个恼人问题的方法:

  • 确保您已明确安装已弃用的独立软件包,例如conda install -c conda-forge joblib six
  • 创建一个将覆盖依赖项导入的假导入,例如:
try:
    sys.modules['sklearn.externals.six'] = __import__('six')
    sys.modules['sklearn.externals.joblib'] = __import__('joblib')
    import hdbscan
except ImportError:
    import hdbscan

如果没有导入错误,将使用独立的 6 和 joblib。否则,例如如果用户没有安装 six 或 joblib,该程序仍然可以运行(因为它从 sklearn.externals 加载了两个模块),但它会显示折旧警告。