はじめに
このシリーズは計算化学を題材にした Python プログラミングを紹介した「計算化学者のためのPython入門」の1つです。
特に「計算化学関連の Python プログラム開発に携わる予定」の読者を想定してます。
Python3 は Google Colab を使えばブラウザだけで実行 できます。
本記事の要点
- これは小-中規模プログラムを開発した経験に基づく ポエム です。追記予定です。
- プログラムは丁寧に読んで理解しましょう。自分が知らないコードはなるべく使わないようにしましょう。
- 共同開発する場合、暗黙のルールがあることが多いです。ルールを知り、正しく開発しましょう。
- 質問するときは「答え」ではなく「解決までの手順」を聞きましょう。
「このプログラム、読んでおいて~」
プログラムを読む(コードリーディング)ときに気をつけていることを紹介します。
1. 便利なツールを使って楽をする
コードリーディングためには、テキストに色をつけれくれたり、検索してくれる高機能エディターが非常に便利です。
EmacsやVim、サクラエディタでも構いませんが、世の中に存在するツールを使いこなせるようになれば非常に便利です。
Pycharmでは、エディタ機能、入力補完機能、変数チェック機能、デバッグ機能などがあり、Jupyter Notebookも利用することができます。
私自身、昔は「Emacsでも同じようなことができるだろ」と思っていましたが、おしゃれさはPycharmに勝てません。普段遣いするなら、使っていて楽しい、おしゃれなエディターがいいのかなって思います。
ちなみに、Pycharmは教育ライセンスがあり、学生や教育関係者であれば無料でProfessional版を利用することができます。
2. 目的を見失わない
プログラムを読んでいると、目的を見失ってしまうことがよくあります。
私はプログラムを読む目的を2つに分類し、以下のようなことに気をつけています。
-
ユーザーとしてプログラムを理解するためのコードリーディング
- 「プログラムを隅々まで理解する」よりも「大雑把にプログラムの流れを把握する」ことを意識
- マニュアルやドキュメントと照らし合わせながら、プログラム実行から正常終了までの流れを追う
-
開発者としてプログラムに機能を実装するためのコードリーディング
- 関連するプログラム内に動作や機能がわからない関数、ライブラリがなくなるまで熟読
- Python referenceやライブラリドキュメントと照らし合わせながら、機能を確認する
3. プログラム構造を把握するコツ
プログラムを把握するためには、プログラムの粒度を意識するといいと思います。
例えば、以下のようなプログラムの場合、上と下は入出力(IO)だと推測できます。
from hoge import main_program
from fuga import IO
io = IO()
io.initialize()
# Load parameters from an external file
parameters = io.load_parameters(xxxxx)
# ---------------------------------------------------------------
# これより上は入力情報を取得しているだけ。
# 重要なのはparametersのみ。printなどしてデータ形式を確認すればよい。
# ---------------------------------------------------------------
# Main Program
data = main_program(xxxxx)
# ---------------------------------------------------------------
# これより下は出力しているだけ。
# ---------------------------------------------------------------
# Save results
io.save_results(data)
プログラム構造が把握できれば、目的に応じて読む領域を決めていきます。
もし、目的が「ユーザーとしてプログラムを理解する」であれば、IOは読まず、hoge.pyのmain_program関数のみ眺めるだけで十分でしょう。
目的が「開発者としてプログラムに機能を実装する」であったとしても、main_program関数内で完結するのであれば、IOを熟読する必要はありません。
プログラム構造を把握できれば、コードリーディングの範囲を絞ることができます。
初心者の場合、プログラムの構造を把握することが難しいかもしれません。その場合は、有識者に聞いたり、ドキュメントやプログラム内のコメントを丁寧に読むのがよいでしょう。プログラムを読む場合、条件分岐やループの意味を把握することも有効です。
4. 複雑そうなコードはなるべく読まない
初心者が陥りやすい状況として、プログラム全体を隅から隅まで理解するためにいろいろと調べたはいいが、よくわからなくなってやる気をなくしてしまう、というものがあります。
特に、IOのようなシステムに関するコードは煩雑になることが多く、開発者や開発思想を理解していなければ理解できないこともあります。
このような状況に直面した場合には、とりあえず読み飛ばしたり、返り値やその他のoutputとして得られる情報を理解することに専念したほうが良いと思います。近くに有識者がいればすぐに聞くべきです。
初心者の方は逃げるは恥だが役に立つのマインドでコードリーディングに励みましょう。
5. printしながら挙動を確かめる
例えば以下のようなコードがあったとします。どのように読み進めますか?
natoms = len(xyz)
npxyz = np.array(xyz)
dmat = np.zeros((natoms, natoms))
for i, iatom in enumerate(npxyz):
jatoms = npxyz[i + 1:]
iatoms = np.repeat([iatom], len(jatoms), axis=0)
d_irow = np.linalg.norm(iatoms - jatoms, axis=1)
dmat[i][i + 1:] = d_irow
dmat = dmat + dmat.T - np.diag(dmat)
return dmat.tolist()
私はとりあえずググる前に以下のようにしてみます。
print(xyz) # 出力してデータ形式を確認。
natoms = len(xyz)
npxyz = np.array(xyz)
dmat = np.zeros((natoms, natoms))
print(dmat) # np.zerosがわからない…とりあえず出力。
for i, iatom in enumerate(npxyz):
print("------------- ", i, iatom) # enumerate もとりあえず出力。
jatoms = npxyz[i + 1:]
iatoms = np.repeat([iatom], len(jatoms), axis=0)
print(iatoms)
print(jatoms) # とりあえず…
d_irow = np.linalg.norm(iatoms - jatoms, axis=1)
dmat[i][i + 1:] = d_irow
print(dmat)
print(np.diag(dmat)) # とりあえず…
dmat = dmat + dmat.T - np.diag(dmat)
このようにとりあえずprint
した後に、わからない文法や概念をググります。
こうすることで、ググるキーワードのヒントになったり、自分の勘違い、思い込みを防げます。
また、問題を簡単にすることも有効です。私だったら、例えば以下のようにします。
xyz = [[0.0, 0.0, 1.0],
[0.0, 0.0, 2.0],
[0.0, 0.0, 3.0]] # 手計算できそうなデータを入力。
natoms = len(xyz)
npxyz = np.array(xyz)
dmat = np.zeros((natoms, natoms))
...
問題を単純にして、実際にプログラムを動かすことがコードリーディングにおいて重要だと思います。
最後にプログラムを動かすときに注意するポイントを紹介します。
- 変数の型やサイズ
-
type
やlen
で確認
-
- 関数の引き数・返り値は何が入っているか
- ドキュメントや docstrings(def hoge... の下に書かれているコメント)を読む
- if で分岐する条件
- 条件文を
print
/True
になったときの挙動を調べる
- 条件文を
- for ループは何を回しているか
-
print("------")
のようなパーテーションで出力を見やすく
-
「○○を実装してみようか」
これまでに培ったプログラム実装のコツを共有します。
1. 仕様が固まるまで書かない
プログラムは直感的に書いても正しく動くことがあります。しかし、大抵の場合、そのようなプログラムは例外に弱いです。
また、何も考えずにプログラムを書くと、当初の目的とはまったく違う機能を実装してしまうこともよくあります。
そのようなことで時間を無駄にしないためにも、プログラムを書き始める前に、ある程度仕様を考えましょう。
私の経験上、**「◯◯はしない/今は実装しない」**という仕様を考えるのが重要だと思います。
例えば、「Gaussianのlogファイルから計算結果を抜き出す」プログラムを書きたいとき、私は以下のようなコードを最初に書きます。
# Initial parameters
gaulog = ""
# -------------------------------------------
# Load Gaussian log file
# -------------------------------------------
gaulog_lines = "" # readlines()
# -------------------------------------------
# Gaussian log parser
# -------------------------------------------
while xxxxx:
if "Energy":
energy=""
elif "Gradient":
gradient=""
elif "Hessian":
hessian=""
raise NotImplementedError
elif "DipoleMoment":
dipole=""
raise NotImplementedError
elif "MolecularOrbitals":
mo=""
raise NotImplementedError
else:
pass
# -------------------------------------------
# Print/Save Gaussian log
# -------------------------------------------
print(xxxx)
このように
- 大まかなプログラムの流れをコメントとして書き、
- 必要な条件分岐、ループを書き出し、
- 今回の実装では必要ないが将来的に必要になりそうな項目をraiseでエラーにする
だけで、実際に書かなければならないコードや、時間を使って実装する必要のない機能を洗い出すことができました。
あとはプログラムを書くことだけに集中できます。
2. 完璧な仕様書はない
研究のためのプログラミングでは、システム開発とは異なり、仕様書のような完成目標図を明確にする必要はない、と思っています。
今後必要になる仕様を網羅するために時間を費やすのであれば、今必要な機能を実装して研究を進めるべきです。
私自身、「研究のためのプログラム開発でも明確な仕様書を作るべきだ」と思った時期もありました。しかし、研究の方向性や時代に合わせて新しい機能を追加していくような中~大規模のプログラム開発では、プログラムを書き始める前から完成図を描くことは不可能だということを悟りました。
もちろん、開発当初から用途が限られているようなスクリプトや、アップデートする予定のないプログラムはこの限りではありません。
ただ、アップデートして使っていく予定のプログラムの場合、その当時で完璧な仕様書を作ったとしても、アルゴリズムの改良や新しい技術の出現によって、その仕様書が根底から崩れ落ちることもあるでしょう。
そのため、仕様を考える際は、完璧よりも汎用性を重視すべきだと思います。
3. 開発グループのルールを守る
共同開発に参加するためには、開発グループのルールを知り、ある程度は妥協して参加することが必要です。
特にこだわりがない人の場合、できるだけ既存のコードの雰囲気とそろえるのが良いと思います。郷に入れば郷に従えです。
一方で、プログラムに慣れてくると、開発者ごとに譲れない思想が現れてきます。そして、時にはその思想が対立し、宗教戦争が勃発することもあります。
私は思想の対立でディスカッションすることは好きですが、プログラミングの目的は「バグなく(ある程度速く)正常に動くこと」です。そういうコードであれば何でもOKなはず。
もし、最速コードを書きたいのであれば、ある程度速いコードで研究を進めて結果を出しながら、最速コードにアップデートするのが良いと思います(計算速度に関する研究でなければ)。
とにかく、開発グループのルールは守りましょう。それが共同開発です。
「なんにもわからん…」
初心者はわからないことがあったら質問すべきです。
プログラミングはパズルや謎解きとは違い、じっくり考えれば答えが浮かぶというものではありません。
特に初心者は知識や経験が浅いので、ちょっとしたことでつまずき、プログラミングが嫌になることが多いと思います。
そうならないためにも、わからないことがあったら積極的に質問してください。
1. 回答に困る質問例
- 「どうやって書けばいいですか?」
- 「(自分で書くつもりはないのかな?)」
- 「◯◯がわかりません」
- 「まずググって」
- 「◯◯がエラーで止まります」
- 「エラーメッセージをググって」
- 「プログラムがバグで止まりました」
- 「どんなバグ?(ググって)」
2. 回答しやすい/気持ちがいい質問例
- 「〇〇を調べたけどわかりませんでした」
- 「そういうときは△△でググるといいよ~」
- 「〇〇は試しましたが、まだバグの原因はわかりません」
- 「じゃあ△△を試してみましょうか」
- 「◯◯という機能を実装したいのですが、✕✕で困っています」
- 「△△をググってみて~」
3. 質問する側の心がけ
1. 自分で15分くらい調べる。
2. 調べた結果、何がわかったのか伝える。
3. 「答え」ではなく「解決までの手順」を質問する。
4. 質問テンプレートを埋めることを意識する。
1. 〜がわかりません。
2. 具体的には、〜です。
3. 私は〜と考えました。
4. なぜなら〜です。
このテンプレートは以下の記事を引用させていただきました。
ちょっとした心がけで質問する人もされる人もハッピーになれるはずです。
みんなで楽しい Python ライフを送りましょう!
おわりに
私自身、勉強になった記事や概念を共有します。
-
プログラマーのための原則(2 万字)
- プログラミングにあたり気をつけるべき項目が列挙されています。
- 計算化学(研究)ではなくシステム開発寄りな記事だと思いますが、読む価値はあります。
- 特に以下の項目は研究におけるプログラム開発でも重要だと思います。
- 心に残った原則
- DRY (Don't repeat yourself) → 同じ操作を繰り返すな
- 車輪の再発明をしない → すでに実装されているならそれを使え
- 早期の最適化は諸悪の根源 → 最初から最適化するな
- 銀の弾丸などない → プログラムは自動化するだけ(魔法ではない)