macOS 上で matplotlib を立ち上げると、Python is not installed as a framework というエラーが出ることがあります。

この問題は(環境によるのかもしれませんが)以下の方法で再現することができます。 まず、最もシンプルに Homebrew と pip を使って Python 3 と matplotlib をインストールします。

$ # macOS Sierra (10.12)
$ brew install python3
$ pip3 install matplotlib

matplotlib を読み込み、グラフをプロットします。

$ python3
>>> from matplotlib import pyplot as pl
>>> pl.plot([1, 2, 3, 4], 'bo-')
[<matplotlib.lines.Line2D object at 0x10f7916d8>]
>>> pl.show()

この手順ではプロットが正しく表示されます。 matplotlib のバックエンドは macosx です。

pybind11 から matplotlib を使う

pybind11 は C++ と Python の相互運用を実現するライブラリです。 ここでは pybind11::scoped_interpreter を使って Python 環境を C++ に埋め込み、matplotlib を立ち上げてみます。

#include <pybind11/embed.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

namespace py = pybind11;

int main()
{
    py::scoped_interpreter interpreter;
    py::module pyplot = py::module::import("matplotlib.pyplot");
    py::object plot = pyplot.attr("plot");
    py::object show = pyplot.attr("show");
    std::vector<float> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    plot(v, "o-");
    show();

    return 0;
}

Python 3.6 にリンクし、clang でコンパイルします。

$ # macOS Sierra (10.12)
$ clang sample.cpp -std=c++14 -lc++ -lpython3.6m -I/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/include/python3.6m -L/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib -o sample

このプログラムを起動すると、以下のエラーが表示され終了してしまいます。 エラーは matplotlib.pyplot モジュールを読み込んだ瞬間に発生します。

$ ./sample
libc++abi.dylib: terminating with uncaught exception of type pybind11::error_already_set: RuntimeError: Python is not installed as a framework. The Mac OS X backend will not be able to function correctly if Python is not installed as a framework. See the Python documentation for more information on installing Python as a framework on Mac OS X. Please either reinstall Python as a framework, or try one of the other backends. If you are using (Ana)Conda please install python.app and replace the use of 'python' with 'pythonw'. See 'Working with Matplotlib on OSX' in the Matplotlib FAQ for more information.

At:
  /usr/local/opt/python3/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/matplotlib/backends/backend_macosx.py(17): <module>
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap_external>(678): exec_module
  <frozen importlib._bootstrap>(665): _load_unlocked
  <frozen importlib._bootstrap>(955): _find_and_load_unlocked
  <frozen importlib._bootstrap>(971): _find_and_load
  /usr/local/opt/python3/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/matplotlib/backends/__init__.py(60): pylab_setup
  /usr/local/opt/python3/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/matplotlib/pyplot.py(116): <module>
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap_external>(678): exec_module
  <frozen importlib._bootstrap>(665): _load_unlocked
  <frozen importlib._bootstrap>(955): _find_and_load_unlocked
  <frozen importlib._bootstrap>(971): _find_and_load

Abort trap: 6

エラーの内容は次の通りです。

libc++abi.dylib: 例外が捕捉されなかったため、プログラムを終了しました。 pybind11::error_already_set: RuntimeError: Python は Framework としてインストールされていません。 Python が Framework としてインストールされていない場合、Mac OS X バックエンドは正常に動作しません。 Mac OS X 上で Python を Framework としてインストールするには、Python のドキュメントを参照してください。 Python を Framework として再インストールするか、他のバックエンドを試してください。 (Ana)Conda を使っている場合は python.app をインストールし、python を使っている箇所を pythonw で置き換えてください。 Matplotlib FAQ の ‘Working with Matplotlib on OSX’ で追加の情報を入手することができます。

この場合は ~/.matplotlib/matplotlibrc

backend: TkAgg

と書くと正しく表示できます。 しかし TkAgg と macosx では微妙に挙動が異なるため、macosx バックエンドを使う方法を調べてみました。

Python の Framework ビルドについて

Python の Framework ビルドとは Python.framework のことです。 これはとても重要な情報なのですが、Python on Mac OS X README 以外の場所ではほとんど言及されていないようです。

  • –enable-framework
    If this argument is specified the build will create a Python.framework rather than a traditional Unix install.

  • このオプションが指定された場合、Python のビルドプロセスは UNIX 形式のバイナリではなく Python.framework を作成します。

Homebrew で Python 3 をインストールした場合、実行可能ファイルは /usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework にインストールされます。 これは Framework ビルドです。

さらに、上の pybind11 の例では /usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/libpython3.6m.dylib にリンクしています。それではこのエラーは何なのでしょうか?

このエラーは誰が出力しているのか

Framework ビルドかどうかのチェックは _macosx.mverify_framework 関数で行われます。

#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
#define COMPILING_FOR_10_6
#endif

なので、通常は COMPILING_FOR_10_6 が定義されているはずです。 この場合、verify_framework

static bool verify_framework(void)
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    NSRunningApplication* app = [NSRunningApplication currentApplication];
    NSApplicationActivationPolicy activationPolicy = [app activationPolicy];
    [pool release];
    switch (activationPolicy) {
        case NSApplicationActivationPolicyRegular:
        case NSApplicationActivationPolicyAccessory:
            return true;
        case NSApplicationActivationPolicyProhibited:
            break;
    }
    return false;
}

というような処理を実行します。 ここで確認しているのはアプリケーションのプロパティなので、 エラーメッセージが言っているのは「Python を組み込みモードで使う場合は、ホストアプリケーションをバンドル形式で作成せよ」ということです。

参考: https://github.com/matplotlib/matplotlib/blob/v2.1.2/src/_macosx.m

NSApplicationActivationPolicy

アプリケーションをバンドル形式で作成することの意味は、NSApplicationActivationPolicy のドキュメント を読むと明らかになります。 以下では日本語訳のみ記載します。

  • NSApplicationActivationPolicyRegular
    このアプリケーションは通常の app で、Dock に表示され、ユーザーインターフェースを表示することができます。 これはバンドル形式のアプリケーションのデフォルト値ですが、Info.plist を使って設定を上書きすることができます。

  • NSApplicationActivationPolicyAccessory
    このアプリケーションは Dock に表示されずメニューバーを持ちませんが、プログラムまたはウィンドウのクリックによってアクティブにすることができます。 この動作はアプリケーションの Info.plist で LSUIElement キーの値が 1 に設定されている場合に対応します。

  • NSApplicationActivationPolicyProhibited
    このアプリケーションは Dock に表示されず、ウィンドウを作成したり、アクティブになることができません。 この動作はアプリケーションの Info.plist で LSBackgroundOnly キーの値が 1 に設定されている場合に対応します。 また、この値は Info.plist を持たない非バンドル形式の実行可能ファイルのデフォルトの設定です。

このプロパティは実行時に書き込み可能です。

macosx バックエンドを有効にする

したがって、macosx バックエンドを使う方法は次の二つです。

  1. Python またはアプリケーションをバンドル形式にする。
  2. NSApplication の activationPolicy を実行時に上書きする。

ここでは、activationPolicy を上書きする方法を紹介します。 ソースコードは次の通り修正します。

#import <pybind11/embed.h>
#import <pybind11/pybind11.h>
#import <pybind11/stl.h>
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>

namespace py = pybind11;

int main()
{
    [NSApplication sharedApplication].activationPolicy = NSApplicationActivationPolicyRegular;

    py::scoped_interpreter interpreter;
    py::module pyplot = py::module::import("matplotlib.pyplot");
    py::object plot = pyplot.attr("plot");
    py::object show = pyplot.attr("show");
    std::vector<float> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    plot(v, "o-");
    show();

    return 0;
}

次のオプションを使ってコンパイルします。

$ clang sample.mm -std=c++14 -lc++ -lpython3.6m -framework AppKit -I/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/include/python3.6m -L/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib -o sample

これで macosx バックエンドを使うことができます。めでたし。

参考資料