今さらPython3 (42) - クラスの続きの続き

第6章はまだまだ続きます。

入門 Python 3

入門 Python 3

ダックタイピング

これも初めて聞いた言葉なので、念のためwikiへのリンクを載せておく。

ダック・タイピング - Wikipedia
ポリモーフィズム - Wikipedia
ポリリズム - Wikipedia

3つ目は、この記事に関係ないが意味を取り違えていたので、備忘の意味で置かせてもらったw。

説明を読むより、コードを見た方が早いだろう。

>>> class Quote():
...     def __init__(self, person, words):
...         self.person = person
...         self.words = words
...     def who(self):
...         return self.person
...     def says(self):
...         return self.words + '.'
... 
>>> class QuestionQuote(Quote):
...     def says(self):
...         return self.words + '?'
... 
>>> class ExclamationQuote(Quote):
...     def says(self):
...         return self.words + '!'
... 
>>> 

まずはソースを読んでみると、QuestionQuote、ExclamationQuoteの2つのクラスは、Quoteクラスを継承していて、says()メソッドをオーバーライドしているのは分かるよね。

>>> hunter = Quote('Elmer Fudd', "I'm hunting wabbits")
>>> print(hunter.who(), 'says: ', hunter.says())
Elmer Fudd says:  I'm hunting wabbits.
>>> hunted1 = QuestionQuote('Bugs Bunny', "What's up, doc")
>>> print(hunted1.who(), 'says: ', hunted1.says())
Bugs Bunny says:  What's up, doc?
>>> hunted2 = ExclamationQuote('Daffy Duck', "It's rabbit season")
>>> print(hunted2.who(), 'says: ', hunted2.says())
Daffy Duck says:  It's rabbit season!
>>> 

それぞれ実行してました。フツーのオーバーライドの話ですよね?同じsays()メソッドで異なる動き、つまりドットで終わるか、クエスチョンマークで終わるか、びっくりマークで終わるか。これが伝統的なポリフォーリズムなんだそうで。

これが継承などせずにできるというのが本題なんだね。

>>> class BabblingBrook():
...     def who(self):
...         return 'Brook'
...     def says(self):
...         return 'Babble'
... 
>>> brook = BabblingBrook()
>>> 
>>> def who_says(obj):
...     print(obj.who(), 'says', obj.says())
... 
>>> who_says(hunter)
Elmer Fudd says I'm hunting wabbits.
>>> who_says(hunted1)
Bugs Bunny says What's up, doc?
>>> who_says(hunted2)
Daffy Duck says It's rabbit season!
>>> who_says(brook)
Brook says Babble
>>> 

who_says()関数を理解すれば良いんだよね。objという引数にクラス名が入っていて、print文でobjで指定したクラスのwho()とsays()の2つのメソッドが実行されたと理解すれば、きれいに説明がつきそう。

特殊メソッド

先頭と末尾がダブルのアンダースコアになっているのが特殊メソッドという理解で良いのかな。これも例を使って確認していこう。

>>> class Word():
...     def __init__(self, text):
...         self.text = text
...     def equals(self, word2):
...         return self.text.lower() == word2.text.lower()
... 
>>> first = Word('ha')
>>> second = Word('HA')
>>> third = Word('eh')
>>> 
>>> first.equals(second)
True
>>> first.equals(third)
False
>>> 

word2の後ろにtextがあるのが確認が必要だけどやろうとしていることは、小文字に揃えて文字列比較。同じならTure、違うならFalseを返すというシンプルなもの。

>>> class Word():
...     def __init__(self, text):
...         self.text = text
...     def equals(self, word2):
...         return self.text.lower() == word2.lower()
... 
>>> fourth = Word('He')
>>> fifth = Word('hE')
>>> fourth.equals(fifth)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in equals
AttributeError: 'Word' object has no attribute 'lower'
>>> 

怒られて初めて気づく。first...fifthまでに入っているのはテキストではなくて、Wordクラスのインスタンスなんだよね。だからそれぞれtextというインスタンス属性を持っているので、word2.text.lower()という表現で正しいってこと。

話が逸れたところで、元に戻ると、equals()というメソッドを__eq__()にしてあげると、普通に==演算子で比較が出来るようになるらしい。

>>> class Word():
...     def __init__(self, text):
...         self.text = text
...     def __eq__(self, word2):
...         return self.text.lower() == word2.text.lower()
... 
>>> first = Word('ha')
>>> second = Word('HA')
>>> third = Word('eh')
>>> first == second
True
>>> first == third
False
>>> 

特殊メソッドという内容の文書をググるのはこれら。

3. データモデル — Python 3.4.3 ドキュメント
3. Data model — Python 3.5.1 documentation

セクション3.3の特殊メソッド以降のセクションに、この本の中でまとめられている表と同じような情報がある。(ただし、表にはなってない)

余談だけど、eq, ne, lt, ge, le, ge みたいな特殊メソッドに使われている名称って、自分が仕事で使っている某○BAPでは普通に演算子として使えるので、なんだかなじみ深いね。

>>> class Word_ext(Word):
...     def __str__(self):
...         return self.text
...     def __repr__(self):
...         return 'Word("' + self.text + '")'
... 
>>> first = Word_ext('ha')
>>> first       #use __repr__
Word("ha")
>>> print(first)    #use __str__
ha
>>> 

最後の__repr__と__str__を呼ぶところは、面倒なのでWordクラスを継承して、Word_extクラスを定義してメソッドを追加してみた。やってることは一緒でしょ?

コンポジション

これは実例から把握していこう。

>>> class Bill():
...     def __init__(self, description):
...         self.description = description
... 
>>> class Tail():
...     def __init__(self, length):
...         self.length = length
... 
>>> class Duck():
...     def __init__(self, bill, tail):
...         self.bill = bill
...         self.tail = tail
...     def about(self):
...         print('This duck has a ', self.bill.description, ' bill and a ', self.tail.length, ' tail')
... 
>>> tail = Tail('long')
>>> bill = Bill('wide orange')
>>> duck = Duck(bill, tail)
>>> duck.about()
This duck has a  wide orange  bill and a  long  tail
>>> 

Duckクラスの中で、Bill、Tailのインスタンスを属性として取っているんだね。

モジュールとクラスの使い分け

これはPythonに限らず悩ましい選択肢だったりするけど、基本的は単純な方を選べば良いってことかな?

plus.google.com

だいぶ長くなったけど、最後に名前付きタプルで第6章を締めくくろう。

>>> from collections import namedtuple
>>> Duck = namedtuple('Duck', 'bill tail')
>>> duck = Duck('wide orange', 'long')
>>> duck
Duck(bill='wide orange', tail='long')
>>> duck.bill
'wide orange'
>>> duck.tail
'long'
>>> 

確かに、属性ばかりをたくさん持つものをわざわざクラスとして定義するより、名前付きタプルの方が簡単に使いこなせるよね。

>>> parts = {'bill': 'wide orange', 'tail': 'long'}
>>> duck2 = Duck(**parts)
>>> duck2
Duck(bill='wide orange', tail='long')
>>> 

アスタ2つ(**)は、任意の長さの辞書が入るという原則が押さえてあれば、**partsと急に出てきても、あ、そゆことねと受け止められるよね。

>>> duck2 = Duck(bill='wide orange', tail='long')
>>> duck2.bill
'wide orange'
>>> duck2.tail
'long'

これと同じというのは、わざわざ書いてもらわなくても分かる。

>>> duck3 = duck2._replace(tail='magnificent', bill='crushing')
>>> duck3
Duck(bill='crushing', tail='magnificent')
>>> duck3 = duck2._replace(tail='nothing')
>>> duck3
Duck(bill='wide orange', tail='nothing')
>>> duck2 = duck2._replace(tail='very long')
>>> duck2
Duck(bill='wide orange', tail='very long')
>>> 

名前付きタプルは、イミュータブルだけど、_replace()を使えば、1つ以上の項目の書き換えは可能なんだね。本では別の名前付きタプルと書いてあるけど、試した限りではduck2を直接上書き出来ている。

>>> duck_dict = {'bill': 'wide orange', 'tail': 'long'}
>>> duck_dict
{'tail': 'long', 'bill': 'wide orange'}
>>> duck_dict['color'] = 'green'
>>> duck_dict
{'tail': 'long', 'bill': 'wide orange', 'color': 'green'}
>>> duck
Duck(bill='wide orange', tail='long')
>>> duck.color = 'green'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Duck' object has no attribute 'color'
>>> 

最後は辞書との比較。辞書と違って名前付きタプルでは項目は追加できない。てか、決まったデータモデルを処理することを念頭に置いているんだろうから、そこは短所とは言えないよね。

(つづく)

おまけ

ポリフォーリズムという単語を聞くと、脳内で変換されてこのBGMがアタマの中に流れるのは自分だけだろうか?


[MV] Perfume「ポリリズム」