6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

計算化学者のためのPython入門ークラスの使いみちー

Last updated at Posted at 2022-10-17

はじめに

このシリーズは計算化学を題材にした Python プログラミングを紹介した「計算化学者のためのPython入門」の1つです。
特に「計算化学関連の Python プログラム開発に携わる予定」の読者を想定してます。

Python3 は Google Colab を使えばブラウザだけで実行 できます。

本記事の要点

  • 計算化学においてもクラスの使い道はたくさんある。
  • プログラミング原則、DRY (Don't repeat yourself)、を意識しましょう。
  • 執筆者はオブジェクト指向初心者です。お手柔らかにお願いします。

オブジェクト指向で頻出単語

クラス、メソッド、インスタンス、コンストラクタなどの頻出単語をまとめます。
最初は何を意味しているのかわかりにくいと思いますが、がんばって慣れましょう(カタカナ覚えられない…)。

  • クラス(class)
    • 学級というよりも、「」という意味に近いと思います。
    • ポケモンなどの概念を一括りにした類、というイメージです。
  • コンストラクタ(constructor)
    • construct で「構築する」という意味です。
    • クラスを構築するときに最初に呼ばれるメソッドのことを指します。
  • メソッド(method)
    • これは def hoge(): で定義するメソッドと同じ、「関数」を意味する。
    • クラス内に定義された関数もメソッドと呼ぶ。
  • インスタンス(instance)
    • For instance で「例えば」という意味ですね。instance は実例という意味があります。
    • オブジェクト(object、実体、物) とも呼ばれる。

【Python入門】クラスの基本を1から解説する―完全版

クラスやコンストラクタ、メソッド、または実際に利用する際のインスタンスは以下のように使います。

test.py
class Pokemon():  # クラスの定義方法
    def __init__(self, name, level, type1, type2=None):  # コンストラクタ:初期設定
        self.name = name     # self.xxxx とすることでクラス内で利用可能になる
        self.level = level   # 詳細は上記のリンクへgo
        self.type1 = type1
        self.type2 = type2
        return

    def show_type(self):  # メソッドを定義
        print("type1 = ", self.type1)
        if self.type2 is not None:
            print("type2 = ", self.type2)
        return

    def greeting(self):  # self が必要な理由:上記のリンクへgo
        print("I'm " + self.name + ". Lv. " + str(self.level) + "!")
        return

# ---- Main program
pikachu = Pokemon("Pikachu", 81, "Electric")  # インスタンス pikachu を定義
pikachu.show_type()   # インスタンス(主語)、メソッド(動詞+目的語)のように使うと見通しが良い
pikachu.greeting()
print("---")
kabigon = Pokemon("Snorlax", 75, "Normal")
kabigon.show_type()
kabigon.greeting()
output of test.py
type1 =  Electric
I'm Pikachu. Lv. 81!
---
type1 =  Normal
I'm Snorlax. Lv. 75!

(ざっくりとした)オブジェクト指向

オブジェクト指向プログラミングとは、

  • いろいろな機能(メソッド)が詰まったクラスを、
  • まるで変数(オブジェクト、インスタンス)のようにみなす/扱うことで、
  • プログラムの見通しを良くしたり、保守性を高めることができる
  • プログラミングの流儀

のことです。

より詳細には「カプセル化」や「ポリモーフィズム (polymorphism: 多相性)」、「継承と抽象化」という概念があります。

以下の記事がわかりやすかったので共有します。

クラスの使いみち(ポケモン)

上のプログラムの簡易版をオブジェクト指向とそうじゃない流儀で書いてみましょう。

まずはオブジェクト指向です。

class Pokemon():
    def __init__(self, name, level):
        self.name = name
        self.level = level
        return

    def greeting(self):
        print("I'm " + self.name + ". Lv. " + str(self.level) + "!")
        return

# ---- Main program
pikachu = Pokemon("Pikachu", 81)
pikachu.greeting()
print("---")
kabigon = Pokemon("Snorlax", 75)
kabigon.greeting()

ここで「クラスをオブジェクトのように扱う」というのは、pikachu = Pokemon("Pikachu", 81, "Electric") の部分を指します。greeting() という挨拶機能を持ったPokemonクラスをあたかも変数pikachuとして扱っています。

このように、ポケモンとして共通の機能(greeting)を持った個体(実体:オブジェクト)(Ex. pikachuやkabigon)をつくるという考え方がオブジェクト指向です。このようにすると、他のポケモンを扱うためにはどうすればいいのか、という見通しがつきやすくなります

実はこのプログラムはオブジェクト指向を使わなくても書けます。

def pikachu(name, level):
    print("I'm " + self.name + ". Lv. " + str(self.level) + "!")
    return

def kabigon(name, level):
    print("I'm " + self.name + ". Lv. " + str(self.level) + "!")
    return

# ---- Main program
pikachu("Pikachu", 81)
print("---")
kabigon("Snorlax", 75)

ただし、このように書いてしまうと、プログラミング原則「DRY (Don't repeat yourself)」に反してしまいます。
つまり、print("I'm " + self.name + ". Lv. " + str(self.level) + "!")をポケモンの数だけ書く必要があります。

こうなってしまうと、挨拶メッセージを変えたくなった場合、全部の行を間違いなく正確に修正する必要があります。
この程度のコードであれば、完璧にアップデートできると思いますが、もしこれが数百匹のポケモン全部に対して定義されていた場合、置換するとしても非常に多くの労力を費やすことになります。これでは楽するためのプログラムでかえって疲れてしまいます

オブジェクト指向の場合、Pokemonクラス内のgreeting()メソッドだけを修正すれば、挨拶メッセージの改良に対応できます。このような点で、オブジェクト指向は保守性に優れていると言えます。

クラスの使いみち(計算化学)

では、計算化学の世界ではどのようにオブジェクト指向プログラミングを取り入れればいいでしょうか。

Moleculeクラス

ポケモンと同じ感覚で使われるのがMoleculeクラスです。
分子(Molecule)が持つ共通の性質(原子名、座標、分子量、沸点、融点 など)を返すメソッドを定義してあげれば、アンモニアやベンゼンといったそれぞれの実体をオブジェクトとして扱えるようになります。

例えば、PySCFプログラムではMoleクラスが定義されています。

抽象化で子クラスの機能を縛る

クラスの機能として、抽象化と継承があります。詳細は省きますが、サンプルコードを見ればなんとなく意味がわかるはずです。

計算化学に限らず、プログラマーがよく遭遇する状況に「◯◯と✕✕の機能はほぼ同じなんだけど、△△だけ違う」というものがあります。
例えば、

  • ◯◯と✕✕は、SCF手続きの流れは共通だけど、△△の部分だけ異なる。
  • 第一原理MD計算の初期条件サンプリング法の◯◯と✕✕は、ほぼ共通だけど、エネルギー分配の方法だけ違う。
  • いろいろな電子状態計算 (ESC) プログラムの結果をxxxxプログラム形式に変換したい。

のような状況です。

ここでも意識すべき原則は「DRY (Don't repeat yourself)」です。例えば、最後の ESCparser のサンプルプログラムを書いてみましょう。

このプログラムはESCの出力からenergy、gradientを取得することが求められているとします。このように異なるクラスだけれども、共通する機能が必要な場合、抽象化が役に立ちます。

from abc import ABCMeta, abstractmethod

class ESCparser(metaclass=ABCMeta):  # 抽象クラスを定義
    @abstractmethod                  # この下に定義したメソッド(energy)は
    def energy(self):                # 継承先の子クラスで必ず実装しなければならない
        raise NotImplementedError

    @abstractmethod
    def gradient(self):
        raise NotImplementedError

class GaussianParser(ESCparser):     # ESCparserを継承した子クラスを定義
    def __init__(self):
        xxxx
        return

    def energy(self):                # これを定義しないとエラーになる
        xxxx
        return

    def gradient(self):              # これも定義しないとエラーになる
        xxxx
        return

class GAMESSParser(ESCparser):       # 抽象クラスの継承で機能を"縛る"
    def __init__(self):
        xxxx
        return

    def energy(self):
        xxxx
        return

    def gradient(self):
        xxxx
        return

    def hessian(self):               # 新しいメソッドの追加は可
        xxxx
        return

このようにすることで、GaussianParserGAMESSParserの機能(メソッド)を縛ることができました。

この抽象クラスと継承は共同開発において非常に役に立ちます

つまり、抽象クラスを定義した開発者は今後MolproParserを作る開発者に必要な機能を知らせることができます。一方、新規開発者は迷うことなくenergygradientメソッドの実装に取り掛かることができます。マニュアル化するのは面倒だけどプログラムの仕様として重要な部分は抽象クラスを使って縛るのが良いかもしれません。

情報を格納する箱

クラスの他の使い道として情報を受け取る箱として使うというものがあります(かなりPythonicなのでわかりにくいかもしれませんが)。もしかしたらPySCFもそのように使っているかもしれません。

Pythonではリスト型や辞書型、json形式で書き出す、などいろいろな方法で情報を格納することができます。ただ、これらの場合、格納した情報にアクセスするためには、事前知識が必要になることが多いです。

例えば、以下のように。

list_data = save_data2list()    # データをlistに格納
print(list_data[0][0][0])       # xxxx はどの階層にあるのかな?

dict_data = save_data2dict()    # データをdict(辞書)に格納
print(dict_data["ESC"]["xxxx"]) # key(鍵)を知っていればデータにアクセス可能

save_data2json("save.json")         # データをjson形式で出力
dict_data = load_json("save.json")  # いちいち読み書きが必要

このようなことを避けるために、クラスを使うことができます。

ただ、この記事が対象としているPython初心者が扱える範疇を越えているような気がするので、参考書を紹介するにとどめたいと思います(車輪の再開発を避けた)。

おわりに

クラスやオブジェクト指向は初心者には難しい(かもしれない)概念です。

一度でわからなくてもいいと思います。何度も遭遇して、自分で少しずつ理解するのが良いと思います。

リンク

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?