【Python】matplotlib で問題が発生したときの解決方法

Python の matplotlib で、問題が発生したときの解決方法を書きました。

ModuleNotFoundError や OSError が出た

matplotlib をバージョンアップして、新しく matplotlib 3.4.2 を使おうとしました。

OS は Windows 10 で、Python は 3.8.6 でした。

そうしたら、ModuleNotFoundErrorOSError が出て、Python プログラムが止まりました。

問題の Python コード例です。1行だけです。

import matplotlib.pyplot as plt

実行結果です。

matplotlib.pyplot をインポートすることができませんでした。

なにやら、'cairo' というモジュールが見つからない、とありました。

Traceback (most recent call last):
  File "***\Python38\lib\site-packages\matplotlib\backends\backend_cairo.py", line 15, in <module> 
    import cairo
ModuleNotFoundError: No module named 'cairo'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "***/jisaku_main.py", line 2, in <module>
    import matplotlib.pyplot as plt
  File "***\Python38\lib\site-packages\matplotlib\pyplot.py", line 2500, in <module>
    switch_backend(rcParams["backend"])
  File "***\Python38\lib\site-packages\matplotlib\__init__.py", line 621, in __getitem__
    plt.switch_backend(rcsetup._auto_backend_sentinel)
  File "***\Python38\lib\site-packages\matplotlib\pyplot.py", line 257, in switch_backend
    switch_backend(candidate)
  File "***\Python38\lib\site-packages\matplotlib\pyplot.py", line 277, in switch_backend
    class backend_mod(matplotlib.backend_bases._Backend):
  File "***\Python38\lib\site-packages\matplotlib\pyplot.py", line 278, in backend_mod
    locals().update(vars(importlib.import_module(backend_name)))
  File "***\Python38\lib\importlib\__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "***\Python38\lib\site-packages\matplotlib\backends\backend_gtk3agg.py", line 5, in <module>
    from . import backend_cairo
  File "***\Python38\lib\site-packages\matplotlib\backends\backend_cairo.py", line 21, in <module> 
    import cairocffi as cairo
  File "***\Python38\lib\site-packages\cairocffi\__init__.py", line 48, in <module>
    cairo = dlopen(
  File "***\Python38\lib\site-packages\cairocffi\__init__.py", line 45, in dlopen
    raise OSError(error_message)  # pragma: no cover
OSError: no library called "cairo" was found
no library called "libcairo-2" was found
cannot load library 'libcairo.so.2': error 0x7e
cannot load library 'libcairo.2.dylib': error 0x7e
cannot load library 'libcairo-2.dll': error 0x7e

'cairo' というのは、matplotlib が "backendバックエンド" と呼んでいるものでした。

現象の原因

もしかしたら、matplotlib が『デフォルトのバックエンド』を変更したときの影響を受けたのかもしれません。

(Matplotlib) cairo backend defaults to pycairo instead of cairocffi (What’s new in Matplotlib 3.1)

バックエンドの説明は、matplotlib のチュートリアルにありました。

(Matplotlib) What is a backend? (Usage Guide)

(Matplotlib) Selecting a backend (Usage Guide)

(Matplotlib) The builtin backends (Usage Guide)

解決方法

(Matplotlib) Selecting a backend (Usage Guide) の説明にあった通り、matplotlib のバックエンドを、Python に標準で付属している TkInter (TkAgg) に変更しました。

これで、エラーが解消しました。

修正後のコード例です。

import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt

修正後の実行結果です。

(エラーは出ませんでした)

成功です。

バックエンドを変更するために使用した関数です。

(Matplotlib) matplotlib.use(backend, *, force=True)

ほかにも解決方法はあると思いますが、自分はこの方法が簡単でした。

以上です。

matplotlib が大量のログを出力した

matplotlib をバージョンアップして、新しく matplotlib 3.4.2 を使おうとしました。

OS は Windows 10 で、Python は 3.8.6 でした。

そうしたら、matplotlib から大量のログが出て、ルートロガーに出力されました。

問題の Python コード例です。

import logging

# ルートロガーを取得します。
lg = logging.getLogger()

def main():
    # ルートロガーに『ロギングレベル』と『ハンドラ』を設定します。
    lg.setLevel(logging.DEBUG)
    sh = logging.StreamHandler()
    sh.setLevel(logging.DEBUG)
    fmt = logging.Formatter('(%(name)s logger, %(levelname)s) %(message)s')
    sh.setFormatter(fmt)
    lg.addHandler(sh)

    lg.debug('--- start ---')

    # matplotlib をインポートします。
    import matplotlib # ← ここで、大量のログが出力されました。
    matplotlib.use('TkAgg')
    import matplotlib.pyplot as plt

    lg.debug('---- end ----')
    return

if __name__ == '__main__':
    main()

実行結果です。

(root logger, DEBUG) --- start ---
(matplotlib logger, DEBUG) matplotlib data path: ***\Python38\lib\site-packages\matplotlib\mpl-data
(matplotlib logger, DEBUG) CONFIGDIR=***\.matplotlib
(matplotlib logger, DEBUG) matplotlib version 3.4.2
(matplotlib logger, DEBUG) interactive is False
(matplotlib logger, DEBUG) platform is win32
(matplotlib logger, DEBUG) loaded modules: ['sys', 'builtins', '_frozen_importlib', ...
    (中略)
..., 'dateutil.parser.isoparser', 'dateutil.parser', 'matplotlib.units', 'matplotlib.dates']
(matplotlib logger, DEBUG) CACHEDIR=***\.matplotlib
(matplotlib.font_manager logger, DEBUG) Using fontManager instance from ***\.matplotlib\fontlist-v330.json
(matplotlib.pyplot logger, DEBUG) Loaded backend TkAgg version unknown.
(root logger, DEBUG) ---- end ----

import matplotlib を実行したときに、matplotlib のロガーが、大量のログを出力していました。

現象の原因

この現象の原因は、matplotlib 3.1.0 で、matplotlib がロギングに『Python 標準の logging ライブラリ』を使用するようになったから、のようでした。

このログ出力の回避方法は、Matplotlib のマニュアルにありました。

(Matplotlib) Other – Logging is now done with the standard python logging library. (API Changes for 3.1.0)

解決方法

'matplotlib' という名前のロガーを取得して、そのロギングレベルを logging.INFO に設定したら、解決しました。

import logging
logging.getLogger('matplotlib').setLevel(logging.INFO)
import matplotlib

修正後の Python コードです。

※ 通常は、matplotlib のインポートや 'matplotlib' のロギングレベル変更を、main 関数の中でする必要はないです。ここでは、実行結果のコンソール表示を得るために、わざと main 関数の中で matplotlib を扱いました。自分は通常、グローバルのところで、matplotlib のインポートやロギングレベルの変更をしています。

import logging

# ルートロガーを取得します。
lg = logging.getLogger()

def main():
    # ルートロガーに『ロギングレベル』と『ハンドラ』を設定します。
    lg.setLevel(logging.DEBUG)
    sh = logging.StreamHandler()
    sh.setLevel(logging.DEBUG)
    fmt = logging.Formatter('(%(name)s logger, %(levelname)s) %(message)s')
    sh.setFormatter(fmt)
    lg.addHandler(sh)

    lg.debug('--- start ---')

    # (対策) 事前に 'matplotlib' の名前でロガーを取得して、
    # そのロギングレベルを INFO 以上に上げておきます。
    logging.getLogger('matplotlib').setLevel(logging.INFO)

    # 以降、この 'matplotlib' のロガーは使用しないので、
    # 変数には入れなくて OK でした。

    # matplotlib をインポートします。
    import matplotlib # ← ログが出なくなりました。
    matplotlib.use('TkAgg')
    import matplotlib.pyplot as plt

    lg.debug('---- end ----')
    return

if __name__ == '__main__':
    main()

修正後の実行結果です。

(root logger, DEBUG) --- start ---
(root logger, DEBUG) ---- end ----

意図した通り、matplotlib のログが、ルートロガーに出なくなりました。

成功です。

ロギング設定の Python マニュアルの場所です。

(Python) logging.getLogger(name=None)

(Python) logging.Logger.setLevel(level)

(Python) class logging.StreamHandler(stream=None)

(Python) logging.Handler.setLevel(level)

(Python) class logging.Formatter(fmt=None, datefmt=None, style='%', validate=True)

(Python) %(name)s %(levelname)s %(message)s(LogRecord 属性)

(Python) logging.Handler.setFormatter(fmt)

(Python) logging.Logger.addHandler(hdlr)

(Python) logging.debug(msg, *args, **kwargs)

自分は、『プログラムの起点きてん(エントリポイント)』となる Python ファイル (.py) で、ルートロガーを使用していました。

それで、matplotlib が、なにやらログを出していたことに気がついて、その抑制方法をしらべるに至りました。

matplotlib のログの内容は、単なるデバッグ情報で、特に気にする必要はなさそうでした。

ところで、matplotlib のログに遭遇しなかった場合もありました。

たとえば、『ルートロガー』を使用していなかった場合は(ルートロガーにハンドラを設定していなかった場合は)、matplotlib のログに遭遇しませんでした。

あと、『名前付きロガー』だけを使用していた場合も、matplotlib のログに遭遇しませんでした。

以上です。

タイトルとURLをコピーしました