diveintopython 6章読書メモ

このメモは、diveintopythonの日本語訳を読んで、個人的にまとめたものです。
個人的に理解した内容なので劣化している部分があると思います。

はじまりはじまり

5章から1ヶ月放置していて若干抜けてます。
何となくやり方を変えて、基本的な説明と、気になったことの記録程度にします。
あんまり長いとキーボード打つのも面倒ですし


前回まではクラスを重点的にやりました。5と6章まででfileinfo.py の解説を網羅するそうです。
6章は、ファイルの取り扱いと、それに伴って必要になる例外処理のお話。fileinfo.pyの解説ではlistDirectory関数の解説を行い全てをまとめてる。

例外処理

Chapter 6. 例外とファイル操作
pythonの例外処理は、try...exceptブロック(文)
try節で例外があるだろう処理を入れて、あるだろう例外をexcept節で指定。
その他に使える節はfinally文、raise文、else文
finally文では、例外が発生してもしなくても最終的に動く節。
else文はif文のと同じ意味。ここでは例外が起こった場合と起こらない場合を分けたときにこれを使う
(ここでの例ではファイルの読み込みとクローズ処理をfinally節で実装してる。他方else節を使っている人もいました。どっちのほうがいいのかな?)
Python: ファイル読み込み時の例外の扱い例 - try、except、else、finallyブロック - Yukun's Blog

raise文はここでは説明がない。pythonのドキュメントでは例外を強制的に発生させるものらしい。
8. エラーと例外
最後に文なのでコロンを忘れずに。

try :
 fo = open("/file/to/path/hogehoge","r")
except IOError :
 print "hogehoge not open error!"

ファイルのオープンや読み込み書き込みは、例外がつきもの。
別のプログラムによってそのファイルがいきなり消えたり、誰か別のユーザが書き込んだりと(排他処理により)自分の持ち物でない可能性がある。
try節で例外がでそうな内容を入れて、exceptででた例外についての処理を書く。exceptの例外指定は複数可能、もちろん例外に対して処理を分けるために複数のexceptも書ける。


例外とは、プログラムに組み込んだ内容から想定以外の事柄の事をいい基本的にエラーが出る物。そのとおりに利用するほかにも、それを利用して他の方法を行う場合がある。特定の環境での代替処理とか。

6.1.1. 他の目的で例外を使用する

##Example 6.2. プラットフォーム固有の機能をサポートする
  # getpass という名前に適当な関数を割り当てる
  try:
      import                      termios, TERMIOS
  except ImportError:
      try:
          import                            msvcrt
      except ImportError:
          try:
              from  EasyDialogsimport  AskPassword
          except ImportError:
                         getpass = default_getpass
          else                                   :
              getpass = AskPassword
      else:
          getpass = win_getpass
  else:
      getpass = unix_getpass

ファイル入出力

6.2. Working with File Objects

もう例外のところで出てきているopen関数は、開きたいファイルパスを引数にして実行するとファイルオブジェクトを返す関数。そのファイルオブジェクトを使って、読み込んだり中身のプロパティを調べたりする。
open関数の引数はファイルパス以外にも、オプションとしてファイルのモード(読み込み、書き込み、テキストかバイナリか)、バッファリングパラメータの指定が行える。モードはとりあえず大事ですね。


ファイルを開いたら読み込む。読み込みには、tellとseekメソッドを使ってファイルのどの位置を読みたいか指定して読み込むとか、全部読み込むreadメソッドや、改行がある場合は1行ごと読み込めるreadlineメソッドとか。

3.9 ファイルオブジェクト

ファイルを書き込む時は、writeメソッドを使う。ファイルモードが'w'か'a'でないとダメ。wは上書きしてaは追記できる。

ファイルを閉じるときはcloseメソッドを使う。
ファイルを開いている間は、そのプログラムが利用中の事だから他のプログラムは排他的処理により使えない。ので開放する必要がある。メソッドを使い開放すると、ファイルオブジェクト側では読み込めないことがわかる。

forループの説明

6.3. Iterating with for Loops

pythonのfor文は、リストの中身だけ繰り返す。数字指定のカウンタとかは普通はしないらしい。はじめて知ったときは衝撃的(他の言語にfor each文があればそれに似ている。)
しかし、リストの中身をそのまま使えるのでわかるととても便利。

##Example 6.8.  for ループの紹介
 >>>li = ['a' ,'b' ,'e']
 >>>for  sin li:        
     ...print s         
a
b
e
 >>>print "\n".join(li)  #リスト読み込み
a
b
e


ちなみに、カウンタをやるなら、range関数を使う。range関数は数値のリストを作成してくれるので、実質的にそうなる。

##Example 6.9. シンプルなカウンタ
 >>>for  iin range(5):             #range(5)を動かすと[1,2,3,4,5]が作られる
     ...print i
0
1
2
3
4
 >>>li = ['a' ,'b' ,'c' ,'d' ,'e']
 >>>for  iin range(len(li)):       #わざわざリスト内の個数を数えてリストの位置指定で出す必要は無い。
     ...print li[i]
a
b
c
d
e

forループ(もといリストのマップ)はあらゆるものを繰り返す。辞書も使えるが例にある。
というか辞書が使えたらだいたいの格納されている事柄は何でも出力できる事になって面白い。
(例えばdir関数でモジュールの関数やメソッドの一覧が見れるけどあれは辞書なので、一覧出力とかも普通にできる。
dir()関数 - バリケンのPython日記 - pythonグループ→さらに便利→Coffee and Ashtray: pythonにsee()を入れる
6.10の例では、OSの環境変数の一覧を出している。for文とリストマップの例がありどちらも出力結果は同じに見える。


forループの使いどころとしては、取り出したリストの内容を関数を使って処理させたい場合なんかだろうか。
リスト読み込みは結果としてリストを返すし、わざわざリストを作る必要がない場合はこれを使う方が良いのかな。

sysモジュールを使う

6.4. sys モジュールを使用する

sysモジュールとは、OSのシステムレベルの情報や行える操作をまとめたモジュールのこと、多分(おい
再帰の深さの最大値まで決まってるなんてのはしなかった!
sys.modulesの中には現在利用されているモジュールの一覧がある。

#Example 6.13.  sysモジュールを使用する
 >>>import fileinfo         1
 >>>print '\n'.join(sys.modules.keys())
win32api
os.path
os
fileinfo
exceptions
__main__
ntpath
nt
sys
__builtin__
site
signal
UserDict
stat
 >>>fileinfo
<module 'fileinfo' from 'fileinfo.pyc'>
 >>>sys.modules["fileinfo"] 2
<module 'fileinfo' from 'fileinfo.pyc'>

sys.modulesは辞書になっている。値がモジュールのリファレンスでキーが名前なので、キーを指定するとモジュールのリファレンスが手に入る。
これをこの章では、MP3以外の定義をしてあるFileinfoクラスを探すために使う。次のosモジュールを使うとそれが実現するとか。

__module__クラス属性とは

組み込みのクラス属性、クラスが定義されているモジュールの名前が出てくる。
つまり、あるクラスに__module__属性を参照すると、そのクラスのモジュール名が分かる。モジュールがファイル名と同じなら追跡が楽になるかもしれない。

os.pathモジュールを使う

6.5. Working with Directories

ここでは、os.pathモジュールでファイルパスの操作を行う。プラットフォームに依存するモジュールだがこれを使うことで、プラットフォーム固有の違いを隠喩してる。
pathというだけにファイルパスの操作を行える。ファイルのフルパスからファイルのパスとファイル名を分離したり、パスとファイル名を統合したり。
今回で大事なところは、拡張子も分離出来る点。これを使って、特定の拡張子の定義をしてあるFileinfoクラスを探す方法を実装している。
これを使うとファイルパスをいじるのが大変楽に思う。こちらが手入力するパスなんかは気を遣うかもしれない。

最後にまとめてる

というわけで、上の解説から今回のfileinfo.py の解説を見てみる。

Example 6.6. MP3FileInfo 内のファイルオブジェクト

        try                                :
             fsock = open(filename,"rb" , 0)
            try                           :
                         fsock.seek(-128, 2)
                   tagdata = fsock.read(128)
            finally                        :
                              fsock.close()
            .
            .
            .
        except                      IOError:
            pass         

MP3FileInfoのファイルオブジェクトを見ると、なんてことない普通のファイル操作。
seek(-128, 2)でmp3ファイルのタグ情報の位置へ移動(どうやらタグ情報はデータの後ろに載っているらしい)、read(128)でそのタグ情報を読み込む。この間で例外をキャッチしたらこの処理自体をパスする。

Example 6.11. MP3FileInfo 内の for ループ

    .
    .
    .
def stripnulls(data):
    "strip whitespace and nulls"
    return data.replace("\00" ,"").strip()
    .
    .
    .
    tagDataMap = {"title"   : ( 3, 33, stripnulls),
                  "artist"  : ( 33, 63, stripnulls),
                  "album"   : ( 63, 93, stripnulls),
                  "year"    : ( 93, 97, stripnulls),
                  "comment" : ( 97, 126, stripnulls),
                  "genre"                                  : (127, 128, ord)}
    .
    .
    .
            if  tagdata[:3] =="TAG":
                for  tag, (start, end, parseFunc)in  self.tagDataMap.items():
                                    self[tag] = parseFunc(tagdata[start:end])

tagDataMapは、タグ情報のどの部分がどの項目(アーティストとかアルバムとか)を示すマップを作ってる。辞書の値がタプルで3番目が解析するための関数の名前になってる。
そのマップを下のロジックに取り込む。
まずfor文でマップ無いのキーと辞書をタプルで分離。paeseFuncには関数名が入り、これを関数として実行することができる。
次に、リストを継承したクラスの中に解析関数で実行した内容を入れる。中身はstart:endでわかるように、タグ情報をマップの数値でスライスして、parseFuncにある関数名の関数を実行している。
stripnullsは、"\00"を消してstrip関数で先頭と松尾部分の空白文字の除去を行う。
・・・あれ、strip関数ってとっても便利ですね。今度コードを書くときに早速使ってみよう。

3.6.1 文字列メソッド

Listdictionary関数詳細

今回のfileinfo.py では、MP3以外のファイルもメタ(タグ)情報も対応出来る仕組みを作ってる。それがlistDirectory関数

Example 6.15. fileinfo.pyないの sys モジュール

    def getFileInfoClass       (filename, module=sys.modules[FileInfo.__module__]) #1
        "get file info class from filename extension"                             
         subclass ="%sFileInfo"         % os.path.splitext(filename)[1].upper()[1:] #2
        return  hasattr(module, subclass)and  getattr(module, subclass)or  FileInfo #3
  • #1

このgetFileInfoClass関数では、与えられたファイル名(ファイルのフルパスではなくて、パスを取り除いてるらしい)と、オプションにmodule変数の引数を持っている。
moduleのデフォルトには、sys.modulesからFileInfoクラス内のモジュール名を入れている。と言ってもこれはオプションで、実際には使っていないことが後で分かる。あくまで定数扱い

  • #2

次に、いわゆる文字列整形が使われている。%sには、os.path.splitext関数により分離されたfilenameの拡張子が入れられている。
その後にupperメソッドは小文字を大文字に変換するメソッド。さらに文字列をスライスして拡張子の大文字だけを残してる。
hoge_audio.mp3というファイル名が来たら、ここのsubclassは"MP3FileInfo"となるはず。"huga_huga.txt"ならTXTFileInfo

  • #3

最後に三項演算子っぽい実装になってる。といっても実際にはgetattrを使ってmodule内のメソッドを探しに行ってる。
今回の場合は、modules(このモジュール内のリファレンス一覧)からsubclassを探して、リファレンスを得ると言うこと。
拡張子が.mp3ならこのモジュール(fileinfo.py)内のMP3fileinfoクラスを参照していることになる。
hasattrは、subclassがそのモジュールに有るか確認を取るために入れているらしい。三項演算子だから、hasattrで存在を確認したらgetattrを実行、そうでないならFileInfoクラスを使うことにしてある。
2.1 組み込み関数のhasattrを参考

ほかのプログラムに応用するならFileInfoの部分をモジュール内の適当なクラスにでもすればいいのかな。

Example 6.19. fileinfo.py 内の辞書をリストする

def listDirectory(directory, fileExtList):
    "get list of file info objects for files of particular extensions" 
    fileList = [os.path.normcase(f)
                for  f in os.listdir(directory)] #1 2
     fileList = [os.path.join(directory, f) for f in fileList
                if  os.path.splitext(f)[1]in   fileExtList] #3 4 5

6.18の上にある処理の話しで、2回のリスト読み込みで指定したディレクトリ内の特定のファイルのリストを作成している。

  • #1,2

dictionary(と言う指定したディレクトリ)より、os.listdir()でディレクトリ内の全てのファイルとフォルダのリストを返して、os.path.normcase関数でパスの大文字小文字をシステムの標準にししたリストをfileListに入れてる。パスを扱いやすくしているというイメージかな。

  • #3,4,5

次でfileExtList(という扱いたいファイルの拡張子のリスト)を使ってリストフィルタリングをして、ファイルのフルパスに戻したリストを格納してる。

これで、対象のディレクトリ内から指定した拡張子のファイルだけを集めた段階になる。

listDirectory関数を見る

def listDirectory(directory, fileExtList):
    "get list of file info objects for files of particular extensions" 
    fileList = [os.path.normcase(f)
                for  f in os.listdir(directory)] 
     fileList = [os.path.join(directory, f)
                for f in fileList
                 if  os.path.splitext(f)[1]in   fileExtList] 
    def getFileInfoClass       (filename, module=sys.modules[FileInfo.__module__]) 
        "get file info class from filename extension"                             
         subclass ="%sFileInfo"         % os.path.splitext(filename)[1].upper()[1:] 
        return  hasattr(module, subclass)and  getattr(module, subclass)or  FileInfo 
    return  [getFileInfoClass(f)(f) for f in fileList] #1
  • #1

この関数の最後に、ファイルごとのメタ情報(の辞書)をリストにして返してる。
getFileInfoClassでファイル名を引数指定すると、そのファイルにあったメタ情報解析クラスが見つかり、
さらに(f)でクラスの引数としてファイル名を与えると、結果としてメタ情報を解析したインスタンスインスタンス自体が辞書)が返ってきて、リスト化されてる。

これで全て解説されました!

長かった・・・(自分がサボってただけ)
最後に見直してみる。

fileinfo.py

このモジュールの最後に、pythonコマンドで実行された場合が書かれている。
if __name__="__main__"がコマンドでの実行時の命令だが、ここではディレクトリと拡張子が指定されている。デフォルトでの指定らしい。
もしコマンドの引数でディレクトリを指定した場合の書き方としてはこんな感じ?(何もチェックしていないけど)

Python: コマンドライン引数の取得 - sys.argv変数 - Yukun's Blog より

.
.
.
if  __name__ =="__main__":
    argvs = sys.argv
    argc = len(argvs)
    print(u"引数にディレクトリ先を指定することで実行されます。\n====")
    if argc != 2 :
		print u"Usage: python %s directory" % argvs[0]
		quit()

    for  infoin listDirectory((argvs[1]), [".mp3"]):
        print "\n".join(["%s=%s"  % (k, v)for  k, vin info.items()])

感想として

ファイルに書き出してもおもしろいかもしれませんね。実用的で本当にモジュールに組み込んでも良さそうなじゃないかと思ったところです。
次は7章ですね。正規表現だ!!