2017/11/17

Python Tips: 改行をうまく扱いたい

Python での改行の扱い方についてまとめてみました。わりとピンポイントなテーマになりますが、興味のある方はご参考にしてみていただければと思います。

  • Python における改行コード
  • 改行なしで出力する
  • ファイルの中身を行単位で取得する
  • 文字列の末尾の改行コードを取り除く
  • 文字列を改行で分割する
  • 改行コードをそのまま出力する
  • ソースコード内で改行する

Python における改行コード


まずは、基本中の基本ですが、今回のお話の前提となる「 Python における改行コード」について見ておきましょう。

Python における改行コードは \n です。

print('五月雨を\nあつめて早し\n最上川')
# =>
# 五月雨を
# あつめて早し
# 最上川
#

print("蛤の\nふたみにわかれ\n行秋ぞ")
# =>
# 蛤の
# ふたみにわかれ
# 行秋ぞ
#

文字列中に \n を入れるとそれが改行コードとして解釈されます。

Python には文字列リテラルの表記が 2 種類ーー通常の引用符と二重引用符がーーありますが、どちらも改行コードに関するふるまいは同じで、改行コードは出力時に改行に変換されます。

改行なしで出力する


組み込みの print() 関数は、デフォルトでは末尾に改行を自動的に追加するふるまいになっています。

print('Hello')
print('world')
# =>
# Hello
# world

この改行を防ぐには 2 の方法があります。

  • a. print() の引数 end を指定する
  • b. print() の代わりに sys.stdout.write() を使う

a. print() の引数 end を指定する

print() 関数の宣言部は次のようになっていて、このうちの end を指定すると、末尾に自動で追加される改行コードを変更することができます。

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

改行なしの出力にしたい場合は単純に空文字を渡せば OK です。

print('Hello', end='')
print('world', end='')
# => Helloworld

b. print() の代わりに sys.stdout.write() を使う

標準ライブラリ syssys.stdout.write()print() と同じように標準出力に書き出すための関数ですが、こちらは末尾に改行を自動追加するようなことはしないので、文字列を渡せばそれがそのまま出力されます。

import sys

sys.stdout.write('Hello')
sys.stdout.write('world')
# => Helloworld

このトピックについては過去にも記事を書いているので、興味のある方はご覧になってみてください。


ファイルの中身を行単位で取得する


テキストファイルの中身を改行までをひとつの区切りとして取得する方法です。方法がいくつかあるのでひとつずつご紹介します。

  • a. ファイルオブジェクトを for ループで回す
  • b. ファイルオブジェクトの readline() メソッドを使う
  • c. ファイルオブジェクトの readlines() メソッドを使う

a. ファイルオブジェクトを for ループで回す

ファイルオブジェクトはイテレータプロトコルを備えているので、ループで回すことができます。その場合の各要素はファイル内の各行となります。

with open(FILE_IN) as f:
    for line in f:
        print(line, end='')
# => ファイルの各行が出力される

各行の末尾には改行コード \n が付いたままになっているので、他のファイルや標準出力に出力する際には末尾の改行が重複しないように気をつける必要があります。

b. ファイルオブジェクトの readline() メソッドを使う

ファイルオブジェクトには readline() というメソッドが備わっており、これを使えば for ループの場合と同様に行単位でファイルの中身を取得することができます。

with open(FILE_IN) as f:
    while True:
        line = f.readline()
        if not line:
            break
        print(line, end='')
# => ファイルの各行が出力される

こちらの場合は readline() の戻り値が空文字列になればファイルの末尾に到達したことになります。ファイル途中の空行の場合は空文字ではなく改行コード \n が 1 つだけ入った文字列が返るので、区別することができます。

a と b を比べると、 pythonic なのはどちらかということ a の方なので、 b で書きたい強い理由が特に無いかぎりは a のパターンで書くのがよいかと思います。

c. ファイルオブジェクトの readlines() メソッドを使う

ファイルオブジェクトの readlines() メソッドは、その名前が示すとおり、ファイルの中身を複数行まとめて取得できるメソッドです。

with open(FILE_IN) as f:
    print(f.readlines())
# => ファイルの全行が行単位で格納されたリスト

ファイルのサイズが巨大な場合にどうなるかは調べていないのでわかりませんが、私が確認したかぎりでは、ファイルの全行を行単位で格納したリストが生成されます。 a 、 b の方法と同じく、こちらの場合も末尾の改行は含まれたままとなっているので注意が必要です。

ちなみに、次の例のように、ファイルオブジェクトの readlines() メソッドに引数を渡すと取得行数を指定することもできます。

with open(FILE_IN) as f:
    while True:
        lines = f.readlines(5):
        if not lines:
            break
        print(lines)
# => ファイルの中身を 5 行ずつ取得したリスト
# ファイルの行数が 5 行よりも少ない場合は、例外などは上がらずファイルの行がすべて格納される

文字列の末尾の改行コードを取り除く


続いて、文字列の末尾の改行コードを取り除く方法です。上の「テキストファイルの中身を行単位で取得する」ときなどに、各行を前処理したい場合などに利用することがあるかと思います。

方法としては、おそらく rstrip() メソッドを使う方法が最もシンプルで、なおかつ間違いがありません。

with open(FILE_IN) as f:
    for line in f:
        trimmed = line.rstrip('\n')
        print(trimmed)
# => ファイルの各行が表示される

ちなみに、 for ループで各行を取得した場合には、行の先頭に改行文字列があることは無いので、ここの rstrip()strip() に置き換えても同じ結果を得ることができます。ただ、 strip() よりも rstrip() を使った方がコードの意図をより明確に示すことができるので、このようなケースでは私は rstrip() を好んで使っています。

文字列を改行で分割する


間に改行が挟まった文字列を改行のところで分解する方法についてです。

こちらは、文字列型の splitlines() メソッドを使うのがよいでしょう。

haiku = '夏草や\n兵どもが\n夢の跡'
lines = haiku.splitlines()
print(lines)
# => ['夏草や', '兵どもが', '夢の跡']

名前そのままの挙動でわかりやすいですね。

ちなみに、 splitlines()keepends という引数を受け取ることができます。これは「改行コードを残すかどうか」を指定するためのフラグで、デフォルト値は False になっています。 True にすると、戻り値のリストの各要素の末尾に改行コードが残ったままになります。

haiku = '夏草や\n兵どもが\n夢の跡'
lines = haiku.splitlines(keepends=True)
print(lines)
# => ['夏草や\n', '兵どもが\n', '夢の跡']

同様の処理は、文字列型の split() メソッドで行うこともできますが、区切り文字が改行コードなら、 splitlines() を使う方が意図をより明確に示せるのでよいかなと思います。

改行コードをそのまま出力する


次は、改行コードをそのまま出力する方法についてです。

改行コードを変換せずにそのまま出力するには、 repr() 関数をかませるか、フォーマット文字列シンタックスの変換フラグ !rrepr() )を使うとよいかと思います。どちらも得られる結果は同じです。

haiku = '石山の\n石より白し\n秋の風'
print(repr(haiku))
# => '石山の\n石より白し\n秋の風'

haiku = '石山の\n石より白し\n秋の風'
print('{!r}'.format(haiku))
# => '石山の\n石より白し\n秋の風'

ソースコード内で改行する


以上のお話はすべて「文字列の中の改行」についてのお話でしたが、これは文字列の中ではなく、 Python のソースコードの中で改行するにはどうすればよいか、というお話です。

Python は、 C 言語のように文の末尾に ; を付ける必要がない代わりに、通常は行末がそのままひとつの文の終わりを意味することになっています。では、メソッドチェーンなどで一文が長くなるときにはどうやって改行したらよいのでしょうか。メジャーなやり方は、大きく分けて 2 つあります。

  • a. 改行を \ でエスケープする
  • b. かっこの中で改行する

a. 改行を \ でエスケープする

コード中の改行を文の終わりにしたくない場合は、 \ (バックスラッシュ)を使えば OK です。改行の直前に \ を置きましょう。

Address.select() \
    .where(Address.geo_lng.is_null(False)) \
    .where(Address.geo_lat.is_null(False))

b. かっこの中で改行する

Python では通常文末が文の終わりを示すことになりますが、かっこの中だけはわりと自由に改行することができます。ここで「かっこ」というのは、 [](){} のことを指しています。

例えば、リスト内包は次のように改行を入れて書くことができます。

[x * 5
    for x in range(10)
    if x % 2 == 0]

また、関数やメソッドの () 内の引数は途中で自由に改行することができます。

Address.select(
    Address.title,
    Address.geo_lng,
    Address.geo_lat)

この方法を使えば、例えば、メソッドチェーンが長くなる場合に、式全体を () でくくることで、各行の行末に毎回 \ を入れる手間を省くことができます。

(Address.select()
    .where(Address.geo_lng.is_null(False))
    .where(Address.geo_lat.is_null(False)))

・・・

というわけで、以上です。

このあたりは基本的なところではありますが、他の言語を長く触っていて久しぶりに Python に戻ってきたときなんかによく忘れていたりするので、パッと確認できるようにしておくと便利かと思います。

Python の改行の扱い方をまとめて押さえておきたいときにぜひ参考にしてみてください。

0 件のコメント: