今日もやってまいりました、「1日1python」のお時間です。(ただし隔週放送)

初心者にとってクラスがよく分からない理由の、もっとも大きなものは「どんなとき、何に使うものなのか分からん」だと思う。ソースは俺。いやマジわかんねーんだって。そこで、クラスの使いみちをひとつ紹介しておこうと思う。実はこないだ作ったコンソールゲーム『一石二鳥』はクラスの修行も兼ねていたので、どうでもいいところにまでクラス、インスタンスを使いまくっていたんだけど、ひとつだけ「お、こりゃクラスを使って大正解なんじゃないかな」と思った部分があったのだ。そこを書く。

作るのは、ゲームのコマンド入力システムだ。『一石二鳥』のシステムでは、フィールドにいるときはだいたい「wで前進」「sで後退」「cでメニューを開く」「saveでセーブ」ができる。それを実現するためにはこんな感じのを書けばよい。

while 1:
    print("w,s,c,saveのどれかを押してね")
    key = input()
    if   key == "w":
        print("前に進んだぜ")
    elif key == "s":
        print("後ろに戻ったぜ")
    elif key == "c":
        print("メニューを開くぜ")
    elif key == "save":
        print("セーブします")
    else:
        print("無効なコマンドです")

うんオーケイだ。でもさあ、フィールドはいっこじゃないし、メニュー画面でだってコマンド入力する場面があるし、そもそも毎回if文を書きまくるのって面倒くさいし見栄えがよくない。面倒くさいから関数化しようぜ!

# フィールドのファイルではこんな感じ
def inputW():
    print("前に進んだぜ")
def inputS():
    print("後ろに戻ったぜ")
def inputC():
    print("メニューを開くぜ")
def inputSAVE():
    print("セーブします")

while 1:
    print("w,s,c,saveのどれかを押してね")
    key = input()
    if key in ["w","s","c","save"]:
        eval("input" + key.upper())()
    else:
        print("無効なコマンドです")
# メニューのファイルではこんな感じ
def inputW():
    print("メニューの上の項目へ移動するよ")
def inputS():
    print("メニューの下の項目へ移動するよ")
def inputZ():
    print("その項目を選択するよ")
def inputSAVE():
    print("セーブします")

while 1:
    print("w,s,z,saveのどれかを押してね")
    key = input()
    if key in ["w","s","z","save"]:
        eval("input" + key.upper())()
    else:
        print("無効なコマンドです")

こういう書き方をすれば、新しいファイルを作るたびに毎回if文を書かなくて済むぜ。まあそのぶん毎回関数を書くハメになっちゃうんだけど、個人的にはif文を書きまくるよりいいと思う。if文を書きまくる奴はひとつのメソッドが長くなっちゃうからね。オライリー本様も、ひとつの関数は画面に収まるくらいが良いと申しておった。

でもさあ、wとかsを入力した時の挙動は場面ごとに違うからこれでいいけれど、saveを入力したときの挙動はつねにセーブなのですよ。なのに毎度セーブのぶんの関数を書くのは面倒だし、何か書きミスがあったとき「あ、この場面でのセーブだけちょっとバグってるじゃん」ってなるし、テストプレイするときsaveを押したときの挙動をテストするのに全場面でsaveを押さないといけないし、「あそうだ、save押したときに『セーブしますよ』っていうメッセージ入れたい」って思ったとき全場面のinputSAVE()に書き加えないといけなくなるじゃん? 面倒じゃん? そこでクラスの登場ですよ。

# これをスーパークラスとする
class Command:
    def foo(self, key, acceptList):
        if key in acceptList:
            eval("self.input" + key.upper())()
        else:
            print("無効なコマンドです")

    def inputW(self):
        pass
    def inputS(self):
        pass
    def inputC(self):
        pass
    def inputZ(self):
        pass
    def inputSAVE(self):
        print("セーブします")
# フィールドのファイルはこうなる
class Field(Command):
    def main(self):
        while 1:
            print("w,s,c,saveのどれかを押してね")
            key = input()
            self.foo(key, ["w","s","c","save"])

    def inputW(self):
        print("前に進んだぜ")
    def inputS(self):
        print("後ろに戻ったぜ")
    def inputC(self):
        print("メニューを開くぜ")
# メニューのファイルはこうなる
class Menu(Command):
    def main(self):
        while 1:
            print("w,s,z,saveのどれかを押してね")
            key = input()
            self.foo(key, ["w","s","z","save"])

    def inputW(self):
        print("メニューの上の項目へ移動するよ")
    def inputS(self):
        print("メニューの下の項目へ移動するよ")
    def inputZ(self):
        print("その項目を選択するよ")

これが今回使った構成だ。サブクラスであるFieldとMenuの中から、入力キーによって振り分けを行うfoo()メソッドを呼び出す。これはスーパークラスCommandにあり、foo()からはCommand内のそれぞれのinputなんちゃらメソッドを呼び出す。だけどそれぞれのサブクラスで、そのサブクラス内で使うinputなんちゃらメソッドは上書きをしてあるので、実行されるのはサブクラス内のinputなんちゃらメソッドのほうだ。saveを入力したときは、サブクラスでは上書きしていないので、スーパークラス内のinputSAVE()メソッドが実行される。上で「inputSAVE()を毎回書くのはヤだな」と言っていた問題はこれで解決する。

ちなみにだけど、上のみっつのクラスは別々のファイルに書くみたいな風に言ってるけども、ひとつのファイルに書いてもふつうに動く。動作を見たい場合は、以下のスニペットを最後に足せば見ることができるぜ。

instance = Field() # あるいはMenu()
instance.main()

以上が、今回クラスを使いまくってみて、とくに「これはクラス使って正解だったんじゃないかな」と思った部分だ。 本文内で何度も「面倒じゃん?」と言っているけども、まさにそれが「クラスってどんなときに使うもんなの」の答えじゃないかと俺は思う。ぶっちゃけクラス使わなくても良いわけですよ。一番最初のif文の構成でも良いわけですよ。でも、面倒じゃんいろいろと? その面倒さを感じるときになったときがクラスに手を出す瞬間なんだと思う。

どうして最近緑の野郎はやたらとクラスの話題ばっかし書きまくってんのかというと、そろそろ「クラスが分からないレベル」を抜け出せそうなんで、つまり、「クラスが分からないレベル」の頃の気持ちが分かるのは今だけだからだ。今のうちに「クラスが分からないレベル」の人にも分かるクラス説明をなるたけ量産しておこうと思って。説明をするとき致命的なのは、説明する側が「なんでコイツは理解できてねーのか」理解できないことだからな。