8. クラスの作成

リストや辞書変数のようにデータ一式を1つの変数として取りまとめて、メソッ ドによりデータを加工できる仕組みを自作したい場合、クラスを定義すること で実現できます。クラスによって定義される変数は、オブジェクトとして参照 されます。クラスを定義することで、リストや辞書変数のような新しいオブジェ クトを自作できると考えれば良いでしょう。リストや辞書変数は、専用メソッ ドが準備されているように、作成したオブジェクトに適用できる専用のメソッ ドを自作することができます。第11回講義の目的は以下のとおりです。

  • クラスを自作する方法を理解する。

  • クラスの初期化ついて理解する。

  • クラスのアトリビュート(クラス変数とメソッド)を定義する方法を理解する。

クラスの定義

クラスを自前で定義することによって、変数やメソッドが一塊となったオブジェ クト(インスタンス)を生成します。文字型変数、リストまたは辞書型変数用の メソッドが準備されているように、自作したクラスで定義したオブジェクトは、 適用できるメソッドを持っています。オブジェクトが保持しているデータやメ ソッドは、 アトリビュート(属性) と呼ばれ、オブジェクトに紐付けられ ています。

def __init__(self, arg1...)はクラスを定義してオブジェクトが生成される 時に呼ばれる処理(コンストラクタ)です。引数をとることもできます。クラス 変数は、self.aのようにself.からはじまる変数で定義されます。メソッドは、 Method_A(self)のように、引数としてselfをもつ関数としてクラス内で定義さ れます。

example-01
class NewClass():                 # 新しいクラスの定義
    def __init__(self, arg):      # クラスを定義したときの処理
        self.a = arg              # インスタンス変数a
        self.b = 0                # インスタンス変数b
        c = 0                     # クラス内変数、外部から参照できない

    def Method_A(self):           # 適用できるメソッドAの定義
        return

    def Method_B(self, args1):    # 適用できるメソッドAの定義
        pass


d = NewClass(3)                   # インスタンスの生成

注釈

クラスを定義することによって、複数のアトリビュート(クラス変数とメソッ ド)を一塊(オブジェクト)として取り扱うことができます。標準で準備されて いるリストや文字型は、専用のメソッドを利用できるのと同じように、デー タ型を自前で定義して専用のメソッドを自作できると考えれば良いしょう。 クラスを定義して、変数をオブジェクトとして扱うことで、データや一連の 処理をブラックボックス化できるメリットです。

注釈

クラスを継承して新しいクラスを定義することができます。例えば、既存の プログラムで定義されているクラスを修正して新しいクラスを定義するした い時に使われます。個人で作成する1000行程度のプログラムでは、あまり使 う場面も少ないので講義では扱いません。

クラス変数

クラス変数は、全てのオブジェクト毎に定義されます。例えばクラス変数とし て化学式(formula)をもつMolecularクラスを定義してみます。

example-02
class Molecular():
    def __init__(self, formula):   # クラスの初期化
        self.formula = formula     # クラス変数, 化学式


a = Molecular("H2O")               # インスタンスa生成
b = Molecular("CH3OH")             # インスタンスb生成

print(a.formula, b.formula)        # 化学式
a.formula = "CH3COCH3"             # aのクラス変数をアセトンに変更
print(a.formula, b.formula)        # b.formulaは変更されない

オブジェクトaのクラス変数formulaを変更してもオブジェクトbのクラス変数 formulaは変更されません。

クラス内でself.によりクラス変数を定義しなくても、作成したインスタンス にクラス変数を追加することができます。以下の例では、インスタンスを定義 した後に、クラス変数hogeを追加しています。

example-03
class Molecular():
    def __init__(self, formula):                   # クラスの初期化
        self.formula = formula                     # クラス変数, 化学式


a = Molecular("H2O")               # インスタンスa生成
a.hoge = 3                         # クラス変数hogeの追加

このようにして新しいクラス変数をどこでも追加することができます。しかし、 このようなクラス変数の追加は、プログラム中の一ヶ所でまとめて行うべきで す。クラス変数の追加をプログラム中のいろいろなところで行ってしまうと、 後々、作成したオブジェクトがどのようなクラス変数を持っているかわからな くなってしまいます。このため、クラスの初期化処理でのみクラス変数を定義 することをお勧めします。このようなルールを決めておけば、オブジェクトが どのようなクラス変数をもっているかわからなくなっても、classの__init__()を 見れば一目ですぐにわかります。

クラス変数として、リストや文字、辞書型変数も使うことができます。以下 Molecular()クラスで生成したオブジェクトは、元素名と数、分子量等のクラ ス変数と紐付けられます。このように、クラスを定義することで複数のデータ を関連付けて管理することができます。

example-04
class Molecular():
    def __init__(self, formula):                   # クラスの初期化
        self.formula = formula                     # クラス変数, 化学式
        self.elements = {"C": 0, "O": 0, "H": 0, } # クラス変数, 辞書型
        self.name = ""                             # クラス変数, 文字
        self.alias = []                            # クラス変数, リスト
        self.weight = 0                            # クラス変数, 辞書型

メソッド

リストや辞書型変数と同じように、自作するメソッドも引数と返り値をもちま す。分子数を返すメソッド(CalcMolNumber(self))を作成してみます。メソッ ドの定義は、最初の引数として必ずselfとしていることに注意してください。

example-05
class Molecular():
    def __init__(self, formula, mass):    # クラスの初期化
        self.formula = formula            # クラス変数, 化学式
        self.mass = mass                  # クラス変数, 質量(g)
        self.mw = 0                       # クラス変数, 分子量(g/mol)
        self.mol = 0                      # クラス変数, モル数

    def CalcMolNumber(self, mol):         # 分子数を計算するメソッド
        num = mol * 6.02214129e23         # 分子数を計算
        return num


a = Molecular("H2O", 20)                  # オブジェクトa生成
n = a.CalcMolNumber(18)                   # メソッドでmwとmolの更新
print("分子数: {:e}".format(n))

次に定義したオブジェクトの分子量と質量を変更するメソッドを考えてみます。 分子量と質量を変更するとモル数の変わるので一緒に更新します。

example-06
class Molecular():
    def __init__(self, formula, mass):    # クラスの初期化
        self.formula = formula            # クラス変数, 化学式
        self.mass = mass                  # 質量(g)
        self.mw = 0                       # クラス変数, 化学式
        self.mol = 0                      # モル数

    def CalcMolNumber(self, mol):         # 分子数を計算するメソッド
        num = mol * 6.02214129e23         # 分子数を計算
        return num

    def UpdateMw(self, mw):               # 分子数を更新するメソッド
        self.mw = mw                      # 分子量のクラス変数を更新
        self.mol = self.mass / self.mw    # モル数の更新

    def UpdateMass(self, mass):           # 質量を更新するメソッド
        self.mass = mass                  # 質量の更新
        self.mol = self.mass / self.mw    # モル数の更新


a = Molecular("H2O", 20)                  # オブジェクトa生成
a.UpdateMw(18)                            # メソッドでmassの更新
print("分子量: {:.4f}".format(a.mw))
print("モル数: {:.4f}".format(a.mol))

わざわざメソッドUpdateMw()を使わなくても、以下のようにself.mwと self.molを直接代入することもできます。

example-07
a.mw = 18
a.mol = 18/a.mw

このような方法でクラス変数を更新することはよくありません。なぜなら、分 子量(a.mw)のみ変更して、モル数(a.mol)の更新を忘れる可能性があるからで す。このように連動するクラス変数が存在する場合、メソッドを使って更新し た方が安全ですし処理をブラックボックス化できます。

注釈

self.mw = 0(初期値)のまま、UpdateMass()を呼び出すとエラー (ZeroDivisionError: division by zero)になります。self.mw == 0の場合は、 UpdateMass()でFalseを返す等、エラー対策を行った方が良いです。

クイズ

Q1

以下のクラスを使って2つの研究室のインスタンスを生成する。

class Laboratory():                 # Laboratoryクラスの定義
    def __init__(self, labname):     # コンストラクタの定義
        self.labname = labname       # 研究室名前
        self.student = []            # 学生名のリスト
        self.faculty = []            # 教員名のリスト
        self.n1 = 0                  # 学生数
        self.n2 = 0                  # 教員数

    def AddStudent(self, name):
        """
        学生を追加するメソッド
        """
        self.student.append(name)
        self.n1 = len(self.student)

    def AddFaculty(self, name):
        """
        教員を追加するメソッド
        """
        self.faculty.append(name)
        self.n2 = len(self.faculty)

    def CalcGraduate(self):
        """
        受け入れ大学院生の数を返すメソッド
        教員数*3人分受け入れることができる。
        """
        n = self.n2 * 3
        return n

2つの研究室のデータは以下のとおり。

研究室名: 忖度2研
教員: 斎藤恭三, 非不眠
学生: ふなっしィ, SMAPY, サンシャイン池崎山

研究室名: インスタ映10研
教員: 大窪貴ノ花
学生: 小谷翔平, ドナルドトランポリン, 安倍晋一, ブルゾンちえこ

作成したインスタンスのクラス変数を参照して以下のように2研と10研の学生数を表示する。

研究室忖度2研の学生数は3人
研究室インスタ映10研の学生数は2人
class Laboratory():                 # Laboratoryクラスの定義
    def __init__(self, labname):     # コンストラクタの定義
        self.labname = labname       # 研究室名前
        self.student = []            # 学生名のリスト
        self.faculty = []            # 教員名のリスト
        self.n1 = 0                  # 学生数
        self.n2 = 0                  # 教員数

    def AddStudent(self, name):
        """
        学生を追加するメソッド
        """
        self.student.append(name)
        self.n1 = len(self.student)

    def AddFaculty(self, name):
        """
        教員を追加するメソッド
        """
        self.faculty.append(name)
        self.n2 = len(self.faculty)

    def CalcGraduate(self, name):
        """
        受け入れ大学院生の数を返すメソッド
        教員1人あたり3人

        """
        n = self.n2 * 3
        return n


a = Laboratory("忖度2研")          # 2研用のインスタンス生成
a.AddFaculty("斎藤恭三")
a.AddFaculty("非不眠")
a.AddStudent("ふなっしィ")
a.AddStudent("SMAPY")
a.AddStudent("サンシャイン池崎山")

b = Laboratory("インスタ映10研")   # 10研用のインスタンス生成
b.AddFaculty("大窪貴ノ花")
b.AddStudent("小谷翔平")
b.AddStudent("ドナルドトランポリン")
b.AddStudent("安倍晋一")
b.AddStudent("ブルゾンちえこ")

print("研究室{}の学生数は{}人".format(a.labname, a.n1))
print("研究室{}の学生数は{}人".format(b.labname, a.n2))

Q2

引数として出力するファイル名(自分で適当に決める)を与えると、以下のよう な内容をファイルに出力するメソッドをLaboratoryクラスに追加する(2研を出 力した場合です)。

--------------------
研究室名:忖度2研
教員数:2名
学生数:3名
--------------------
教員リスト
1: 斎藤恭三
2: 非不眠
学生リスト
1: ふなっしィ
2: SMAPY
3: サンシャイン池崎山
class Laboratory():                 # Laboratoryクラスの定義
    def __init__(self, labname):     # コンストラクタの定義
        self.labname = labname       # 研究室名前
        self.student = []            # 学生名のリスト
        self.faculty = []            # 教員名のリスト
        self.n1 = 0                  # 学生数
        self.n2 = 0                  # 教員数

    def AddStudent(self, name):
        """
        学生を追加するメソッド
        """
        self.student.append(name)
        self.n1 = len(self.student)

    def AddFaculty(self, name):
        """
        教員を追加するメソッド
        """
        self.faculty.append(name)
        self.n2 = len(self.faculty)

    def CalcGraduate(self, name):
        """
        受け入れ大学院生の数を返すメソッド
        教員1人あたり3人

        """
        n = self.n2 * 3
        return n

    def OutputMember(self, outfile):
        """
        教員と学生のリストを出力するメソッド
        最終行に受け入れできる大学院生の数を出力する
        """
        o = open(outfile, "w", encoding="utf-8")
        o.write("-"*20 + "\n")
        o.write("教員数:{}\n".format(self.n1))
        o.write("学生数:{}\n".format(self.n2))
        o.write("-"*20 + "\n")
        o.write("教員リスト\n")
        for i, s in enumerate(self.faculty):
            o.write("{}: {}\n".format(i + 1, s))
        o.write("学生リスト\n")
        for i, s in enumerate(self.student):
            o.write("{}: {}\n".format(i + 1, s))
        print(outfile, "was created.")


a = Laboratory("忖度2研")
a.AddFaculty("斎藤恭三")
a.AddFaculty("非不眠")
a.AddStudent("ふなっしィ")
a.AddStudent("SMAPY")
a.AddStudent("サンシャイン池崎山")

b = Laboratory("インスタ映10研")
b.AddFaculty("大窪貴ノ花")
b.AddStudent("小谷翔平")
b.AddStudent("ドナルドトランポリン")
b.AddStudent("安倍晋一")
b.AddStudent("ブルゾンちえこ")

a.OutputMember("hoge.txt")