それからのPython 3

はじめに

準備

kujira_classというMODを新しく作ります。

フォルダ構成

└─kujira_class
    └─Assets
        └─Python
            │─KujiraEventManager.py
            │
            └─Entrypoints
                 └─CvEventInterface.py

KujiraEventManager.py

これもリセットしておきます。

from CvPythonExtensions import *
import CvEventManager
import CvUtil

gc = CyGlobalContext()

class Hito:
    def getAge(self):
        return 18
    
class MyEventManager(CvEventManager.CvEventManager, object):

    def onGameStart(self, argsList):
        'Called at the start of the game'
        super(self.__class__, self).onGameStart(argsList)
        ##########

型を作る

今日はを新しく自作します。
まずその部分のコードを見てみましょう…

>>>>>>>>>>
class Hito:
    def getAge(self):
        return 18
<<<<<<<<<<

これはHito型という型の定義です。
実際に使うところも見てみましょう…

>>>>>>>>>>
a = Hito()
age = a.getAge()
<<<<<<<<<<

2つとも代入文ですね。
a = Hito()でHito型の値を作りaに代入しています。
age = a.getAge()では「Hito型の変数aの」関数であるgetAge()を呼び出しています。
getAge()はreturn文で18を返しますから、ageには18が代入されます。

変数の中身は直接見ることはできません。
文字列に埋め込んで、表示してみましょう…

from CvPythonExtensions import *
import CvEventManager
import CvUtil

gc = CyGlobalContext()

class Hito:
    def getAge(self):
        return 18
    
class MyEventManager(CvEventManager.CvEventManager, object):

    def onGameStart(self, argsList):
        'Called at the start of the game'
        super(self.__class__, self).onGameStart(argsList)
        ##########

        a = Hito()
        age = a.getAge()
        
        message = "age=%d" % age
        CyInterface().addImmediateMessage(message, "")


いいですね!

クラス

今使ったのはクラスという仕組みです。
class クラス名:とすると、直後のブロックの内容がクラスとして定義されます。
クラス名は同時に型名にもなっていて、あとでクラス名()とするとその型の値を生成することができます。
クラス型の値ひとつひとつを特にインスタンスと呼びます。
クラス→型、インスタンス→値、と対応しています。ついてきていますか?

クラス定義に戻りまして、ブロックには関数の定義が並びます。
大体普通の関数と同じなのですが、def getAge(self):の行を見ると、
第1引数がselfになっていることが見て取れます。
このselfの中身が、実はインスタンスになっています。

…………どういうことでしょうか?
少し上で「age = a.getAge()では『Hito型の変数aの』関数であるgetAge()を呼び出しています。」と説明をしました。
意味合い的には「aの年齢を取得する」になるわけです。
なので、getAge()を処理するにあたって「自分は誰なのか?」に当たる情報、
自分の持ち主たるインスタンスaの情報を利用できるように、
selfaが代入された状態でgetAge()が呼び出されるわけです。
(わかりずらいでしょうか…わからなくてもとりあえず読み進めてください)

クラス定義ブロックの中にある関数のことを特にメソッドとよびます。
普通にメソッドを定義するとインスタンスの所有物になり、
そのようなメソッドをインスタンスメソッドと呼びます。
インスタンス.インスタンスメソッド名(追加の引数)とすることで
インスタンスメソッドを呼び出すことができます。
インスタンスメソッドの第1引数selfはインスタンスだというのは書いた通りですが、
ただのインスタンスではなく「この関数を呼び出した所有者たる」インスタンスなので、
特にレシーバと呼ぶこともあります。

そして、civ4MODのやりたいことのほとんどが
このインスタンスメソッドの仕組みにより行われます。たとえば、

  • 都市pCityの人口を4にする
  • プレイヤーpPlayerの文明IDを取得する
  • ユニットpUnitに昇進eを持たせる

これらはそれぞれ、

  • pCity.setPopulation(4)
  • pPlayer.getCivilizationType()
  • pUnit.setHasPromotion(e, True)

このようなインスタンスメソッドの呼び出しになるわけです。

インスタンス変数

Hito型に戻りましょう。
HitoクラスのインスタンスメソッドgetAge()は、今のことろ
レシーバのselfを無視して、常に18を返しています。
これではやや味気ないので、インスタンスの情報から年齢を読み取って
返すようにしてみましょう。このようにします。

>>>>>>>>>>
     def getAge(self):
         return self.age
<<<<<<<<<<

インスタンスの所有するメソッドがインスタンスメソッドだったように、
インスタンスの所有する変数をインスタンス変数と呼びます。
インスタンス.インスタンス変数名で代入したり参照したりできます。
ここでは「インスタンスselfの変数age」の値を戻り値として返しています。

self.ageにはまだ値がありません。
そのまま読み出そうとするとエラーになってしまいますから、
代入して設定するためのメソッドを新しく作ります。

>>>>>>>>>>
     def setAge(self, age):
         self.age = age
<<<<<<<<<<

selfはレシーバです。そのほかに、ageという引数をとって、
それを「selfage」に代入します。
asetAge()を呼び出すにはa.setAge(25)のようにします。
呼び出すときにはレシーバselfは自動的に設定されますので、
self以外の引数を指定しましょう。

ここまでをまとめてプログラムしてみましょう…

from CvPythonExtensions import *
import CvEventManager
import CvUtil

gc = CyGlobalContext()

class Hito:
    def getAge(self):
        return self.age
    
    def setAge(self, age):
        self.age = age
        
class MyEventManager(CvEventManager.CvEventManager, object):

    def onGameStart(self, argsList):
        'Called at the start of the game'
        super(self.__class__, self).onGameStart(argsList)
        ##########

        # aさんをつくる
        a = Hito()

        # aさんを25歳にする
        a.setAge(25)

        # aさんの年齢を聞いてageに代入する
        age = a.getAge()
        
        message = "age=%d" % age
        CyInterface().addImmediateMessage(message, "")

ゲームを開始して…

できているようです。

自己紹介させる

インスタンス変数やインスタンスメソッドは同じ要領でいくつでも作ることができます。
Hito型に名前を持たせてみましょう。

>>>>>>>>>>
    def getName(self):
        return self.name
    
    def setName(self, name):
        self.name = name
<<<<<<<<<<

これで、a.setName("Warrior")a.getName()などとできるようになりました。
もうすこし複雑な例として、名前と年齢から自己紹介っぽい文字列を作るメソッドを見てみましょう。

>>>>>>>>>>
    def getSelfIntro(self):
        s = "I'm %s, %d years old." % (self.name, self.age)
        return s
<<<<<<<<<<

これらを使って…

from CvPythonExtensions import *
import CvEventManager
import CvUtil

gc = CyGlobalContext()

class Hito:
    def getAge(self):
        return self.age
    
    def setAge(self, age):
        self.age = age

    def getName(self):
        return self.name
    
    def setName(self, name):
        self.name = name

    def getSelfIntro(self):
        s = "I'm %s, %d years old." % (self.name, self.age)
        return s
        
class MyEventManager(CvEventManager.CvEventManager, object):

    def onGameStart(self, argsList):
        'Called at the start of the game'
        super(self.__class__, self).onGameStart(argsList)
        ##########

        # aさんをつくる
        a = Hito()

        # aさんを25歳にする
        a.setAge(25)

        # aさんをジョンにする
        age = a.setName("John")

        # 自己紹介を表示
        message = a.getSelfIntro()
        CyInterface().addImmediateMessage(message, "")

こんな感じでしょうか。

ゲームを開始して…

いいですね!

コンストラクタ

いまのHito型は、年齢と名前をまず設定しないといけません。
設定しないまま年齢を取得しようとしたり自己紹介を生成しようとすれば
「値がないのに取り出そうとした」のエラーになってしまいます。
どうせ必ず設定しなければいけないのなら、最初にHito型のインスタンスを作るときに
a = Hito("Emily", 35)みたいにできれば忘れることもなく安心なような気がします。
それを実現するには、__init__()( 前と後ろにアンダーバー(_)2つずつ )というメソッドを定義します。

>>>>>>>>>>
    def __init__(self, name, age):
        self.name = name
        self.age = age
<<<<<<<<<<

そうすると、a = Hito("Emily", 35)と書けるようになり、
逆にa = Hito()とは書けなくなります。

インスタンスを作るときに呼び出されるメソッドをコンストラクタと呼び、
コンストラクタが要求している(self以外の)引数をすべて与えないと
インスタンスを作ることができなくなります。

この仕組みによって、インスタンスにとって必須なインスタンス変数を
設定し忘れるエラーを未然に防げるため、べんりです。

ふえるインスタンス

インスタンスは1つであるとは限りません。

>>>>>>>>>>
        a = Hito("John", 25)
        b = Hito("Emily", 35)
        c = Hito("Montezuma", 7)
<<<<<<<<<<

むしろこのようにどんどん作っていく方が便利に使えます。
同じクラス(型)から複数の異なるインスタンス(値)が作れるので、
都市型のインスタンス、ユニット型のインスタンス、プレイヤー型のインスタンス
などなどに応用が利くのですね。

Hito型のインスタンスも3人ほど作って、それぞれに自己紹介させてみましょう…

from CvPythonExtensions import *
import CvEventManager
import CvUtil

gc = CyGlobalContext()

class Hito:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def getAge(self):
        return self.age
    
    def setAge(self, age):
        self.age = age

    def getName(self):
        return self.name
    
    def setName(self, name):
        self.name = name

    def getSelfIntro(self):
        s = "I'm %s, %d years old." % (self.name, self.age)
        return s
        
class MyEventManager(CvEventManager.CvEventManager, object):

    def onGameStart(self, argsList):
        'Called at the start of the game'
        super(self.__class__, self).onGameStart(argsList)
        ##########

        a = Hito("John", 25)
        b = Hito("Emily", 35)
        c = Hito("Montezuma", 7)

        messageA = a.getSelfIntro()
        CyInterface().addImmediateMessage(messageA, "")
        messageB = b.getSelfIntro()
        CyInterface().addImmediateMessage(messageB, "")
        messageC = c.getSelfIntro()
        CyInterface().addImmediateMessage(messageC, "")

ゲームを起動して…

できました!