対話環境
ここまでは、サンプルファイルによるPythonの実行を見てきましたが、pythonコマンドを引数なしで実行すると、対話実行の環境を利用することができます。これは、ちょっとした構文のテストをするのに便利です。
% python Python 2.5 (r25:51908, Jun 5 2007, 17:15:05) [GCC 4.0.1 (Apple Computer, Inc. build 5250)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> 10 ** 3 1000 >>> a = 10 >>> b = a + 2 >>> b 12
>>>の後に好きなコマンドを入力するだけで、逐次評価されていきます。式の評価の結果値が返って来た場合は、その内容が次の行にプリントアウトされるようになってます。
オブジェクト指向
Pythonは、純粋オブジェクト指向言語だと言われています。これは、登場するすべてのデータがオブジェクトであるという意味です。整数や浮動小数点、文字列がオブジェクトであるのはもちろん、関数やメソッド、クラスでさえもオブジェクトです。さらに、importで読み込んだモジュールも実はオブジェクトとなっており、変数に入れたりすることが可能です。Pythonでは、他言語のオブジェクト指向で利用できる手法のほとんどを使うことができますが、ここでは、その中でも特に筆者が気になった部分を中心に紹介していきます。
クラス定義
Pythonにおいて、ユーザ定義クラスはclass クラス名(親クラス)
のように定義します。Javaなどでは親クラスを略せますが、Pythonでは歴史的な事情から略せません。親クラスを省略してしまうと、旧スタイルのクラスと呼ばれる別のクラス定義になってしまいます。
class Person(object): def __init__(self, name='', mail=''): self.name = name self.mail = mail def hello(self): print "Hello, this is %s(%s)." % (self.name, self.mail)
__init__
がコンストラクタです。ここで、インスタンス変数を初期化します。Pythonのオブジェクトは、任意の名前のインスタンス変数を格納可能なので、self
に直接指定するだけで初期化は完了となります。ここで登場した、name=' '
やmail=' '
の形の書式はデフォルト値の指定です。引数にこれらの値が渡って来ない場合は、デフォルトでイコールの右辺の値が使われます。
ここで特徴的なのが、メソッドには全てself
という第一引数を用意することです。これは、メソッドの第一引数には呼び出し時に利用したオブジェクトが渡ってくるためです。Javaのように自動でthisキーワードが用意されるということがないので、注意が必要です。
利用例を見てみましょう。Person
クラスをインスタンス化して使ってますが、ここでも注意点があります。それは、newキーワードがないことです。Pythonではクラス名に括弧()をつけてコールするだけで、自動で__init__(self)
メソッドが呼ばれてインスタンスが生成されます。
# 実行例) # >>> yamada = Person(name='Taro', mail='taro.yamada@hogehoge.com') # >>> yamada.hello() # Hello, this is Taro(taro.yamada@hogehoge.com).
なお、このPersonコンストラクタの呼び出しでも、等号=を使った記法が使われています。呼び出しの時に等号を使うと、これはキーワード引数による呼び出しと解釈されます。関数の宣言に使われているキーワードで、引数を指定する記法で、引数が多い時に便利です。もちろん、宣言されている順番の通りに引数を指定するときは、この等号は省略可能です。
隠蔽
Pythonに隠蔽の機能はありません。全ての属性には、外からアクセス可能です。ただし、慣習として、アンダースコア(_)から始まるメソッドはプライベートであり、外部から操作しないようにするという暗黙の作法があります。また、アンダースコア(_)が2つついて、かつ、末尾のアンダースコアが2つ未満の変数は、外部から見る場合には難読可されて保存されます。と言っても、その変換ルールさえ分かれば、アクセスすることは可能です。
>>> class PrivateTest(object): ... def __init__(self): ... self.attr1 = 'attr1' # 公開属性 ... self._attr2 = 'attr2' # プライベート属性 ... self.__attr3 = 'attr3' # プライベート属性 ... >>> p = PrivateTest() >>> p.attr1 # パブリックな変数 'attr1' >>> p._attr2 # プライベート変数だが、アクセスは可能 'attr2' >>> p.__attr3 # 属性3は、難読化されているからアクセス不能 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'PrivateTest' object has no attribute '__attr3' >>> p._PrivateTest__attr3 # 難読化の法則さえ知っていれば、アクセスできる 'attr3'
このような"ゆるい"隠蔽は、Javaのような言語による隠蔽がサポートされている言語に慣れている方にはとても不安なものに感じるかもしれませんが、ほとんどの場合はこの方式でうまくいきます。それは、わざわざプライベート変数をいじって混乱を招く開発者は開発グループにはいないという、いわば性善説に近い信念に基づいています。実際、そのような悪意のある開発者がいる場合、それは言語の問題というよりは開発体制の問題でしょう。
多重継承
Pythonは多重継承に対応しています。クラスの定義時に、親クラスをカンマ区切りで複数指定するだけで多重継承となります。
多重継承を使えば、既存クラスへのメソッドの追加、つまり、Mix-inを実現することもできます。
# 走る機能を実装したクラス class Runnable(object): def run(self): print "%s can run." % self.name # Person と Runnable を多重継承したクラス(走れる Person ) class Runner(Person, Runnable): pass # 実行例) # >>> sato = Runner(name='Shota', mail='sato.shota@japan.com') # >>> sato.hello() # Hello, this is Shota(sato.shota@japan.com). # >>> sato.run() # Shota can run.
ダック・タイピング
Pythonでは、ダック・タイピングを前提として設計されています。例えば、イテレータのように振る舞うオブジェクトであれば、そのオブジェクトが何を継承しているかは関係なく、イテレータとして利用可能であるということです。イテレータのように振る舞うオブジェクトは、例えば以下のように作ります。
class CountDown(object): # コンストラクタ def __init__(self, count_from=10): self.count_from = count_from # イテレータとして振る舞うのに必要なメソッド(1) # イテレータ(自分) を返す def __iter__(self): return self # イテレータとして振る舞うのに必要なメソッド(2) # カウントダウンしていく def next(self): count = self.count_from self.count_from -= 1 if self.count_from < 0: raise StopIteration # カウンタが0になったら、反復終了 return count # 実行例) # >>> counter = CountDown(5) # >>> for i in counter: # ... print i # ... # 5 # 4 # 3 # 2 # 1
この例のように、特定のクラスを継承していなくとも、処理に必要な属性(メソッド)がオブジェクトに備わっていれば、どんなオブジェクトでもその処理をさせることができます。
メタクラスによるクラスのカスタマイズ
最初に書いたように、純粋オブジェクト指向のPythonでは、クラスもオブジェクトです。よって、クラスオブジェクトの元となるクラス、つまりメタクラスが存在します。このメタクラスをカスタマイズすると、クラスの生成をカスタマイズできます。
デフォルトのメタクラスは、type
クラスです。このクラスを継承して、カスタムメタクラスを作ることができます。クラスを生成する時にどのメタクラスを利用するかは、クラスの定義時に__metaclass__
属性で指定可能です。
# カスタマイズしたメタクラスの例 # プロパティへのアクセスをロギングする class MyTracer(type): # メタクラスがクラスを初期化するためのコンストラクタ def __init__(cls, name, bases, dct): # 親メタクラスに、クラスを初期化してもらう super(MyTracer, cls).__init__(name, bases, dct) # ロギングできる __getattribute__ メソッドを作り、クラスに組み込む def logging(self, key): print "%s was accessed." % key return super(cls, self).__getattribute__(key) cls.__getattribute__ = logging # MyTracer によってクラスを作らせると、 # プロパティにアクセスされる度にロギングされる class TestClass(object): __metaclass__ = MyTracer # メタクラスの指定 def hello(self): self._first_sentence() self._second_sentence() def _first_sentence(self): print "Hi." def _second_sentence(self): print "How do you do?" # 実行例) # >>> test = TestClass() # >>> test.hello() # hello was accessed. # _first_sentence was accessed. # Hi. # _second_sentence was accessed. # How do you do?
メタクラスは慣れないと難しい概念ですが、使いこなせば柔軟なクラス設計が可能となり、大きな武器となり得ます。