こんにちは、バッヂーです。
先日Pythonのsplitメソッドでしばらく悩んだことがありましたので、忘備録のつもりで書きたいと思います。
全てはreadline()のなせるワザ。splitメソッドは忠実なる下僕
挙動に悩んでいた箇所に直接かかわっていたのはsplitメソッドでしたが、もとをたどるとreadline()が原因でした。
- ファイルを1行読み込むためのreadline()メソッドは「改行コード」も読み込む
私のトラブルの内幕は上のようなことでした。読み込んだファイルはカンマ区切りのテキストファイル。splitメソッドで1行をカンマで分割した最後の要素には改行コードも含まれています。これが原因でした。
わかってみれば当たり前のことなんですが、割とこんな簡単なことにつまづいて、時間がかかってしまうのはよくあることで……すよね?
読み込みサンプルと予想した結果
もう上の項目で解決していることではありますが、いちおうまとめておきたいと思います。
まずはこのようなデータを読み込もうとしました。
カンマ区切りのテキストデータです。わかりやすいように「サクラエディタ」で開いた様子をキャプチャしました。
このファイルに対して次のような作業をしようとしていました。
- テキストファイルを1行ずつ読み込む
- 読み込んだ1行を文字列のリスト型変数linesに格納していく
- 上のリスト変数linesをループし、ひとつづつの文字列変数をsplitメソッドでカンマで分割して新たなリスト型変数itemに格納する
- 上のリスト変数itemのそれぞれの要素を組み込んだ文字列を作成し、表示する
上記4.は実際には違う処理ですが、ここではわかりやすくするためにprintすることにします。予想した実行結果は次のとおりです。
-----------------------------------------------------
■■1■■ブラッドハウンド■■ハンティングビースト■■
-----------------------------------------------------
-----------------------------------------------------
■■2■■ジブラルタル■■防衛爆撃■■
-----------------------------------------------------
-----------------------------------------------------
■■3■■ライフライン■■ケアパッケージ■■
-----------------------------------------------------
プログラムと実際の実行結果
プログラムはこんな感じです。読み込むファイルはデスクトップにある「apex_characters.txt」です。
path = r"C:\Users\ユーザー名\Desktop\apex_characters.txt"
lines = []
with open(path, encoding="s_jis") as f:
while True:
line = f.readline()
if not line:
break
lines.append(line)
for line in lines:
item = line.split(",")
print("-----------------------------------------------------")
print("■■%s■■%s■■%s■■" % (item[0], item[1], item[2]))
print("-----------------------------------------------------")
11行目でリストに格納している1行の文字列を「,(カンマ)」で区切っています。そして13行目で分割したそれぞれの要素を■■で挟んで出力しています。結果をわかりやすくするためにこのようにしています。
実際の実行結果はこのようになりました。
-----------------------------------------------------
■■1■■ブラッドハウンド■■ハンティングビースト
■■
-----------------------------------------------------
-----------------------------------------------------
■■2■■ジブラルタル■■防衛爆撃
■■
-----------------------------------------------------
-----------------------------------------------------
■■3■■ライフライン■■ケアパッケージ
■■
-----------------------------------------------------
見ての通り、最後の要素の表示が期待外れです。
item[2]に改行コードが含まれているため、上記のようにレジェンドのウルト名の直後で改行されています。
readline()は1行「全部」を読み込む
これは5行目で1行読み込むために使っているreadline()が、「1行全てを読み込む」からです。「1行全て」ですから、
上のファイルの1行の最後の(改行コード!)も読み込みます。
改行コードを含んだ1行をカンマで区切るわけですから、分割した最後の要素には、当然改行コードも含まれるわけです。
イメージ的には、
が1行読み込みの結果と思っていました。
とりあえずstrip()つけとけば
ところでこんなときに便利なメソッドがPythonにはあります。stripメソッドです。
- 文字列.strip()
とすると、文字列の前後の空白と改行コードが削除されます。
これを利用してコードを修正してみます。
line = f.readline().strip()
元のコードの5行目に「.strip()」を追加するだけです。
私はスクレイピングでの文字列加工に、とりあえずstrip()をつけておくようにしています。気をつけておくのは「前後」の空白と改行コードということですね。途中の空白と改行コードは削除してくれません。
read()よりreadline()のほうがなんとなく好きです
今回は「readline()メソッドで読み込む1行は改行コードを含む」ということを勘違いしていて、split()で分割した要素が意図しないものになったということを説明してみました。
今回のサンプルプログラムですと、readline()で読んだものをリストに格納してから更に処理しています。「1行を読み込みながら処理していく」ということではないですから、「read()で一気に読み込んでから行で分割する」ということも考えられますよね。
このへんは好みだと思うのですが、私はreadline()で1行ずつ読み込むほうがなんとなく好きです。
それぞれのクセを理解しながら、なるべく悩まないですむようにしたいです。