先日述べたように、最近は複数のクラスやメソッドをいったりきたりしてゲームブック的なプログラムを作ってみようとがんばっている。だがインスタンスの扱いになかなか手間取り咬まれまくったので学んだことを整理し修行とする。今回やりたいことはこうだ。

  • 自分がいる地名fieldNameを取得する。たとえばNagoya。
  • Nagoyaで何が起こるかはクラスNagoyaFieldに書かれているので、fieldNameの値に"Field"をくっつけてNagoyaFieldを呼び出し、インスタンスを生成する。
  • そのとき、Nagoyaの中だけじゃなく全編を通して保持したいデータがあるので(全部で何歩進んだかというデータstep)、クラスDataのインスタンスdataのインスタンス属性として保存する。というわけで、NagoyaFieldのインスタンスを作るときはdataを渡すことにする。
  • NagoyaFieldの中では、stepが+100され(100歩進み)、fieldNameがNagoyaからSuwaになる(NagoyaからSuwaに入る)。…という情報はさっき受け渡したdataの中に保存してもらい、返り値としてもらう。
  • NagoyaFieldのインスタンスは廃棄し、再び最初から…fieldNameの値に"Field"をくっつけてFieldのインスタンスを生成する。このときのfieldNameはdataに保存されたfieldNameなので次はSuwaFieldとなる。
  • SuwaFieldのインスタンスに記録装置dataを渡し、また処理が終わったら返してもらう…。

というのを繰り返す。実際のゲームではFieldインスタンスの中を処理が走っている最中に外部入力を受け付けたりする。

スクリプトを書いてみる。

class Main:
    def start(self):
        fieldName, step = DB.load()
        data  = Data(fieldName, step)
        print("開始時点のdata:", data.fieldName, data.step)
        while 1:
            field = eval(data.fieldName + "Field")(data)
            data  = field.walk(data)
            del field
            if data.fieldName == "Niigata":
                break
        print("start()終了時点のdata:", data.fieldName, data.step)

class DB:
    def load():
        # 実際はDBにアクセスすると思うけどここは単純に
        return "Nagoya", 0

class Data:
    def __init__(self, fieldName, step):
        self.fieldName = fieldName
        self.step      = step

class NagoyaField:
    def __init__(self, data):
        self.fieldName = data.fieldName
        self.step      = data.step

    def walk(self, data):
        self.step     += 100
        self.fieldName = "Suwa"
        data.fieldName = self.fieldName
        data.step      = self.step
        print("Nagoya.walk()終了時点のdata:", data.fieldName, data.step)
        return data

class SuwaField:
    def __init__(self, data):
        self.fieldName = data.fieldName
        self.step      = data.step

    def walk(self, data):
        self.step     += 50
        self.fieldName = "Niigata"
        data.fieldName = self.fieldName
        data.step      = self.step
        print("Suwa.walk()終了時点のdata:", data.fieldName, data.step)
        return data

if __name__ == "__main__":
    main = Main()
    main.start()
# 実行結果(見やすさのため若干成型してる。)
開始時点のdata              : Nagoya  0
Nagoya.walk()終了時点のdata : Suwa    100
Suwa.walk()終了時点のdata   : Niigata 150
start()終了時点のdata       : Niigata 150

俺がとくに詰まっていたのは「記録装置としてdataをインスタンスに渡すのは別にいいんだが、それをどうやって返してもらうんだ?」だった。これはインスタンスに対する誤解からくるもので。なんつーか、インスタンスというのは、生成した瞬間に「いま自分がいる場所」と違うところで勝手に動きだすもんだと思い込んでたんだよな。実際は「自分がいる場所」、すなわち処理が動いている場所はたったひとつで、インスタンスが生成されインスタンスメソッドが実行されたときから「自分」はそのインスタンスの中にいるのだよな。だからその状態でreturnをすれば、「自分」はそのメソッドを出て、そのメソッドを呼んだ場所に帰ってこれるというわけだ。つまり、「いや、自分はインスタンスAにいて、インスタンスBくんが自分のメソッドを動かして値をreturnしたって、それはインスタンスBにしかないんじゃねーの?」と誤解してたわけですね。インスタンスとは、生ける謎のクリーチャーでもなんでもなく、インスタンス属性をディクショナリでもつただの連想配列に過ぎないのだ。ちと恥ずかしいが、俺がそういう誤解をしてたってことは他にもそういう誤解をする人がいるはずだから、書いておくぜ。

……と、ここまで書いて面白いことに気付いた。今回のようなことがしたい場合インスタンスfieldを作る必要はまったくない。 マジでない。クラスDBのメソッドloadを呼び出しているときのように、クラス名とメソッドを名指しで呼び出し、インスタンスdataを渡すだけでも同じ結果が出る。つーかdataすら必要ない。ぶっちゃけディクショナリでよい。インスタンスはいらない。インスタンスの練習をすればするほど「インスタンス使わなくていい」ということがわかっていくのはある種のカタルシスすら感じるよ。 クラス、インスタンスというのはあくまでコードを見易く、オブジェクトを扱い易くするためのテクニック。多分そういうことなんだろうと思う。これはこないだ書いた「クラス・オブジェクトって要するに連想配列のことなんじゃね?」で至った結論と同じだ。クラスは初心者にとっての壁だと身を持って体験していたが、思えばあれを皮切りに理解に加速がついてるなあ。我ながらグッジョブなひらめきだったぜ。