diveintopython 5章読書メモ

読書メモで,本家なんかに書いてあるコードは書かずに,試したものとかを書いておく.本家コードは必要以外リンクとして残すし残さないこともある.面倒がりな人間.
ぶっちゃけただの脳内メモなので,何となく見ている人に向けて書いてあるにしては,人のためにはならんはず.

5章始まる

オブジェクト指向ということで,なまじわかってたつもりでしたが.理解し切れていない部分があると何か読みにくかったんだよなあ.
今回のお題は,フォルダの中にあるmp3ファイルのID3v1.1タグの内容を出力するプログラム.結構実用的.ID3v2.*に対応できたらもっとおもしろそう.(実はもっと汎用的で,ほかのファイルフォーマットのメタデータの配置位置を定義できれば何でも対応できるらしい,6章で実態がわかります.)
ちなみにこのプログラム自体の解説としては6章に続くらしいです.

from module importを使ったモジュールのインポート

5.2. Importing Modules Using from module import
import moduleはモジュール自体をインポートしてその下にある属性をドットで使うというもの.
from module importをすると,ほしい属性だけインポートできる.その際にモジュール名.属性 とかやらなくていい.属性の名前の指定で動く.
詳しくはNoteを参考に
(あとでやるかもしれないけど,asも使えて別名で使うこともできる.たくさんドットで区切ったような複雑なパッケージにはこれが有効)
Pythonのモジュールインポートのしくみ
import moduleとの使い分けとしては,頻繁に使って名前が混同しない場合はfrom〜でいいし,名前が混同しそうならimport moduleでモジュール名で分けてもよい.
これもスタイルの問題なので,コードを見てたら両方のやり方を見るだろうとのこと.

クラスの話し

5.3. Defining Classes
classで定義する.引数も使える.引数なしもできる.doc stringは利用すべき.

class testclass:
"""this class is hogehoge"""
  pass

継承したりもできる.継承元を引数に入れておく

from UserDictimport UserDict
sclass FileInfo (UserDict):

たとえばこれはUserDictというクラスを継承した.このクラスは継承したものがあたかも辞書っぽく使えるようになるクラス.


初期化は,def __init__で定義.init(イニト)はイニシャライズで初期化です.特別なメソッドの位置だったと思う.
コンストラクタ(構築子,インスタンスが作成された時に初期化するときに使うメソッド)のように動くが,そのように定義しただけで,初期化する前にインスタンスは作成されてるからコンストラクタとはいえない.けどそんな風に動く.

えーと間違えてるかもしれないけど,クラス定義にしている引数は継承に使っているので,クラス自体の引数指定は__init__を使う.コンストラクタはそういうものらしい.
以下を参照

もちろん、より大きな柔軟性を持たせるために、__init__() メソッドに複数の引数をもたせることができます。 その場合、クラスのインスタンス生成操作に渡された引数は __init__() に渡されます。例えば以下のように:

>>> class Complex:
... def __init__(self, realpart, imagpart):
... self.r = realpart
... self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)

class FileInfo(UserDict):
"store file metadata"
def __init__(self, filename=None):
UserDict.__init__(self) 1
self["name" ] = filename 2 3


Fileinfoクラスでは,コメント通りにファイルのメタデータを貯めることらしい.
最初の__init__でUserDictの__init__を呼び出してる.
この際の引数self(というか__init__のselfのこと)はクラスから作ったインスタンスのことをいってる.クラス自身ではない.
Pythonではインスタンス内でインスタンス内のメソッド,属性(それをデータ属性と言われる)を使うときにはselfを指定する必要がある.というのも,クラスが呼び出される時と,実際にインスタンスを使っているときとは挙動は違うらしい.
これ以上はクラスに詳しくないので説明できなかった.後でクラスとしての参照とインスタントしての参照の話しがあるっぽいので何となくわかるけど,その二つの参照を分けるときにこの仕組みを使っているらしい.らしい?


ここの方のエントリーで紹介されている通り,Rubyの説明がしっくりきている.
Pythonのクラスのselfの意味 - 牌語備忘録
現在の自分自身の参照という意味を込めてself.


戻って1のこと.UserDict.__init__は継承したUserDictの__init__を呼び出してる.5.3.2でいってるけど明示的に呼び出す必要が有るらしい.Pythonの仕様らしい.
2でUserDictがどのように動くを表したといってるけど,辞書っぽくnameキーにfilenameが入ってる.辞書っぽい振る舞いなのであーそうなのか程度.
3の説明として,__init__は返値は返さない.返す必要も無いのかもしれない?と感想を持った.


5.4. Instantiating Classes
インスタンスを作ると,__class__という属性でクラスのメタデータ?クラスの情報ととらえてる.
__doc__はdoc string.あるクラスから作ったインスタンスが複数有っても,そのdoc strringは同じものを共有するようになってる.


ガベージコレクションGC

5.4.1. ガベージコレクション
Pythonはそれに優れているらしい.
ガベージコレクションとはプログラムが動的にメモリを確保してくれる機能.使わなくなった領域(今回だとオブジェクト(インスタンス内)に割り当てた領域)が有ると自動的にメモリを解放してくれる.解放というか使わないとわかったら破棄して使えるようにする.
実際に例でメモリリークを試みてる.
ある関数で変数にインスタンスを代入してる.その関数内のローカル変数で,fはそのまま残されてる.
次の100回実行されると,そのインスタンスが残るんじゃないかと言われている.が結果は残らない.


なんだかデフォルトなのでありがたみが感じられないような雰囲気.あまりほかの言語の経験も無いのもあって.
Pythonのドキュメントにある1.10 参照カウント法にて,Cだmallocとかfreeでやることが,Pythonでは自動的に見てくれてる.考える必要がある程度減るのは助かることだろう.

辞書っぽいUserDictクラス

5.5. Exploring UserDict: A Wrapper Class
5.5からUsreDictの説明がある.ラッパークラスということで,辞書っぽく見せてるというのはこういう仕組みだと言うことが書かれてる.
diveintopythonの日本語訳だと読んでても頭に入ってこなかった気がして,diveintopythonの超訳版を書かれているid:hamatzさんのDive Into Python (4日目) - 暗号、数学、時々プログラミングを見させてもらった.感謝.


ここでの説明はラッパークラスの紹介が基本で,その実現に特殊メソッドがあるとのことだと思う.
(その前に辞書で行うcopy()とかclear()の)通常メソッドの説明もされてる.copyを実現する時の注意点,クラスとメソッドの参照の違いなどがあるので注意とか.
特殊メソッドは特定の状況や特定の構文が呼ばれると実行されるメソッド.
UserDictなら,test["no1"]の中身を得るような構文をあたかも簡単に実現しているのには,その構文は__getitem__を呼び出し書かれている処理をしているから.

def __getitem__ (self, key):
return self.data[key]

>>>f = fileinfo.FileInfo("/music/_singles/kairo.mp3")
>>>f
{'name':'/music/_singles/kairo.mp3'}
>>>f.__getitem__("name") 1
'/music/_singles/kairo.mp3'
>>>f["name"] 2
'/music/_singles/kairo.mp3'

特殊メソッドを使うと,そのメソッドが実現する構文を簡単(?)に定義して利用することができる.いろいろあるから後で見るのも良さそう.
そもそも,python2.2以降は,組み込みデータ型の辞書である,dictを直接継承できるらしい.どちらを使うかはスタイルの問題なのかその辺は定かではないけど.
ただ単純にこうラッパーなクラスを作って実現している実例だしとてもおもしろい.

特別なクラスメソッド

5.6. Special Class Methods
では実際に今回のお題ではどう扱っているか.

def __setitem__ (self, key, item):1
if key =="name" and item:2
self.__parse(item)3
FileInfo.__setitem__(self, key, item)4

コード内の__setitem__は先祖のメソッド(UserDict.__setitem__)をオーバーライド(上書き)していて,そこに仕掛けを埋め込んでる.
keyが"name"だった場合とitemがある場合(andなので両方ともでTrueなら)インスタンス内の__parseを実行して,親メソッドのFileinfoの__setitem__を実行する.
このときの4は,FileInfo内では何もされてないのでさらに親のUserDictへ探しに行って呼び出してる.


それで,これが実際に動くとどうなるかが,Example 5.15に書かれている.
fileinfoのインスタンス作成時にfilenameが"name"キーの辞書っぽくなり,さらに値が追加されているので__parseが動いて結果が出てくる.結果はタグの内容が辞書にされているようだ.

高度な特殊クラスメソッド

5.7. Advanced Special Class Methods
次の項目はさらに特殊なメソッドの紹介がある.UserDictの中身をさらに見ているけど,辞書を文字列にしたり,インスタンスの比較,オブジェクトの長さ,辞書のアイテムの削除等.
ラッパークラス内での特殊メソッドの紹介だったけど,特殊メソッド自体はどのクラスでも使えるらしい.比較したときには何を比較させればいいのか,長さを見たときはどの長さを見ればいいのかとか.
そのほかにも特殊にカバーされたメソッドを定義することはできるそうで,後々に紹介されるとのこと.

データ属性,クラス属性

5.8. Introducing Class Attributes
データ属性とは,インスタンス内の属性の意味だった.クラス属性はクラスに内蔵された属性のこと.
同じものをインスタンス内で何度定義するなら最初から入れておくのもよい.フォーマットの解析に必要な値とか.
Example 5.17で,ID3v1.0のmp3タグのデータマップを表してる.たぶんmp3ファイルの頭にあるヘッダデータみたいなものを読むときに使う数字群だろう(と予測してる.まだ6章見てない・・・)


クラス属性をインスタンス側で変更すると,クラス自体の値も変わってる.同じクラスを継承したインスタンス側も共有される.
さしずめ定数ともいえるけど変更は可能.Pythonのお約束?

プライベート関数

プライベート要素の概念が言われている.引用すると,

多くの言語のように Python はプライベート要素の概念があります。

* プライベート関数は、そのモジュールの外からコールすることができません。
* プライベートメソッドは、そのクラスのの外からコールすることができません
* プライベート属性は、そのクラスの外からアクセスすることができません

多くの言語とは違って、 Python の関数、メソッド、属性がプライベートかパブリックのどちらであるかは、名前によって完全に分けられます。

とのこと.
さきほどのMP3FileInfoで,__parseが出てきたが,これがプライベートの意味.アンダーバー二つで始めるとそう定義できる.
ただ,特殊メソッドはアンダーバー2つで始まり2つで終わる.慣例らしいので,従わなくてもいいけど混乱するだろうからやめた方がよいとの.

Example 5.19では,メソッドのコールを試みてるが失敗してる.とはいえできない訳ではないが行わない方がよいとの.
ところで__parseをプライベートにする理由はなんだろうと.不用意に呼ばせないためとか?

以上終わり

次は6章.コードの続きを解説.