読者です 読者をやめる 読者になる 読者になる

YAMAGUCHI::weblog

土足で窓から失礼いたします。今日からあなたの息子になります。 当年とって92歳、下町の発明王、エジソンです。

readlines(size)が期待通りの動作をしない

ここを参照すると、

  • readlines([sizehint])

readline() を使ってに到達するまで読み出し、EOF 読み出された行を含むリストを返します。オプションの sizehint 引数が存在すれば、EOFまで読み出す代わりに完全な行を全体で大体 sizehint バイトになるように (おそらく内部バッファサイズを切り詰めて) 読み出します。ファイル類似のインタフェースを実装しているオブジェクトは、 sizehint を実装できないか効率的に実装できない場合には無視してもかまいません。

と書いてある。試しに下のようなファイルを作成してテストをしてみた。

  • aaa.txt
a
aa
aaa
aaaa
aaaaa

でもって

>>> f = open('aaa.txt')
>>> lines = f.readlines(6)
>>> for l in lines:
...     print l,
a
aa
aaa
aaaa
aaaaa

あれおかしいよ。普通にreadlines()した場合と何も変わってないよ?じゃあread()はどうだろうか。

>>> f = open('aaa.txt')
>>> data = f.read(6)
>>> print data
a
aa
a

改行文字\nが1バイトなので、ちゃんと読めてますね。なんかMacOS XPythonでreadline()が使えない云々の記事が結構あるから、それと関係あるかと思ったけど、Debian上のPython2.5でも同様の結果になる。
これはなんで?

追記

mopemopeさんのコメントを読み、実装系による物だと納得しました。その後さらにStringIOを使って試してみました。

>>> import StringIO
>>> input = StringIO.StringIO(open('aaa.txt').read()) # 一旦全部読み込む
>>> lines = input.readlines(6)
>>> for l in lines:
...     print l,
a
aa
aaa

ありゃりゃ、StringIOのreadlines(size)だと改行文字を抜かしたバイトサイズだけ読み込んでます。
ではread()を使った場合は?

>>> import StringIO
>>> input = StringIO.StringIO(open('aaa.txt').read())
>>> data = input.read(6)
>>> print data
a
aa
a

これは期待通り動作しますね。まあそもそも読み込みの時点でバイトサイズを指定して

>>> import StringIO
>>> input = StringIO.StringIO(open('aaa.txt').read(6))
>>> lines = input.readlines()
>>> for l in lines:
...     print l,
a
aa
a

とすれば当然期待通りの動作をしますが、この場合だったら普通にread()した結果をsplit('\n')したほうが早いですね。まあそもそもStringIOを使うのは書き込みとかも含めて楽に使う場合を想定しているので例が悪いのですが。