Python の matplotlib で、問題が発生したときの解決方法を書きました。
ModuleNotFoundError や OSError が出た
matplotlib をバージョンアップして、新しく matplotlib 3.4.2 を使おうとしました。
OS は Windows 10 で、Python は 3.8.6 でした。
そうしたら、ModuleNotFoundError や OSError が出て、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'
という名前のロガーを取得して、そのロギングレベルを 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 のログに遭遇しませんでした。
以上です。