グラフ描画用のモジュールであるpyplotは時々お世話になるのですが、なかなか使い方に慣れずにいました。最近ようやくそのもやもやを解消できたので、気づきのメモとして残しておきます。
※ 「なぜそのような設計になっているのか」ではなく「どのような設計になっているのか」という内容の話になります。
個人的によくわからなかったところ
まずは基本的なグラフ出力です。
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-np.pi, np.pi)
y = np.sin(x)
fig = plt.figure()
plt.plot(x, y)
plt.title("y = sin(x)")
plt.xlabel("x")
plt.ylabel("y")
fig.savefig("output.png")
これで画像が output.png
として出力されるのですが、個人的にこの時の挙動がどうも腑に落ちませんでした。たとえば
-
plt
インスタンスを生成した形跡がない - グラフのタイトルなどを変更するのは
plt
のメソッドを使っていたのに、画像として保存する場合はfig
のメソッドを呼び出している -
fig
のようにfig1
,fig2
, ...を生成しただけでplt
でアクセスするときの対象もfig1
,fig2
, ...と順に変化する
といったあたりです(ちなみに title()
や xlabel()
などがクラスメソッドであるという説は考えていませんでした)。
個人的にはインスタンス生成→メソッド呼び出しの流れに慣れているので、下のようなやり方であったとすれば抵抗なく理解していたと思います。
# これは正常に動作しません。
import matplotlib
import numpy as np
x = np.linspace(-np.pi, np.pi)
y = np.sin(x)
plt = matplotlib.pyplot() # ここでインスタンスを生成してほしい
plt.plot(x, y)
plt.title("y = sin(x)")
plt.xlabel("x")
plt.ylabel("y")
plt.figure.savefig("output.png") # ここはpltインスタンスのメソッドであってほしい
なぜ最初のやり方で正常に実行できるのでしょうか?
そしてなぜ私の想定したやり方ではうまく動作しないのでしょうか?
実際どうなっているのか
最初の正常に動作するスクリプトに登場した要素がそれぞれ何者なのかを確認します(numpyの部分は省略します)。
-
matplotlib
- グラフ描画用のライブラリ
-
pyplot
-
matplotlib
ライブラリの中のモジュール -
import matplotlib.pyplot
等でimportして使用
-
-
plt
-
matplotlib.pyplot
のエイリアス(従ってtypeはmodule) - エイリアスなので好きな名前に変更可
-
-
fig
-
matplotlib.figure.Figure
クラスのインスタンス -
fig = pyplot.figure()
で生成される
-
重要なのは plt
がクラスではなくモジュール ということです。 plt
がクラスであったとすればどこかでインスタンスを生成して欲しいところですが、モジュールゆえに2行目でimportした時既に生成されていたため、突然 fig = plt.figure()
や plt.plot(x, y)
としても問題なく動作していたのですね。
また、これは推測ですが、 fig
インスタンスが生成される際に plt
の中でアクセス先が更新されると考えれば他の疑問点も納得できそうです。
おまけ
さて、pythonにおいてモジュールはシングルトンとして生成されるので、どこで呼び出しても同じidのオブジェクトを参照します。従って、 pyplot
を別の場所で呼び出した際も、既に作成済みのグラフにアクセスすることができます。
たとえば、次の2つのファイルが同一ディレクトリにあったとします。
import matplotlib.pyplot as plt
import numpy as np
def edit_graph():
x = np.linspace(-np.pi, np.pi)
y = np.sin(x)
plt.plot(x, y)
plt.title("y = sin(x)")
plt.xlabel("x")
plt.ylabel("y")
import matplotlib.pyplot as plt_main
import graph
graph.edit_graph()
plt_main.show()
この状態で python main.py
を実行すると、先程と同様の正弦波のグラフが出力されます。つまり、あるスクリプトファイル内で生成・編集したグラフを別のスクリプトファイルで出力するということが、インスタンスの生成・受け渡し無しでできるということです(設計上あまり推奨されない気はしますが)。
matplotlib.figure.Figure
クラス内のメソッドである savefig()
を呼び出す場合も、 fig = pyplot.figure()
を実行した後であれば可能です。
まとめ
今回の疑問は、私自身が plt
を何かしらのクラスorインスタンスだと勘違いしていたことが主な原因でした。なぜそう思っていたのか、今となってはわかりません... plt
がモジュールであるということに気付けば大した問題ではありませんが、改めて意識してみるとより理解が深まると思います。
最後に今回気付いたことのまとめです。
-
pyplot
はモジュールであり、普段はplt
のような名前をつけて利用している -
pyplot
モジュール自体がクラスあるいはそのインスタンスのようにふるまう -
fig = pyplot.figure()
を実行するたびにmatplotlib.figure.Figure
クラスのfig
インスタンスが生成され、そのたびにpyplot
モジュールによるアクセスの対象が移る