2018/01/19

Python Tips: split() を活用したい

Python での文字列操作に便利な split() の使い方についてご説明してみます。

'Hello, world'.split()

split()str 型と正規表現ライブラリにあるので、これらを順番に見ていきましょう。

  • str 型の split() メソッド
  • 正規表現ライブラリの split()

str 型の split() メソッド


str 型の split() メソッドのドキュメントは次のようになっています。

print(''.split.__doc__)
# =>
# S.split(sep=None, maxsplit=-1) -> list of strings
# 
# Return a list of the words in S, using sep as the
# delimiter string.  If maxsplit is given, at most maxsplit
# splits are done. If sep is not specified or is None, any
# whitespace string is a separator and empty strings are
# removed from the result.

str.split() — Python 公式ドキュメント

日本語翻訳では次のように書かれています。

文字列を sep をデリミタ文字列として区切った単語のリストを返します。 maxsplit が与えられていれば、最大で maxsplit 回分割されます (つまり、リストは最大 maxsplit+1 要素になります)。 maxsplit が与えられないか -1 なら、分割の回数に制限はありません (可能なだけ分割されます)。

sep が与えられた場合、連続した区切り文字はまとめられず、空の文字列を区切っていると判断されます(例えば '1,,2'.split(',')['1', '', '2'] を返します)。引数 sep は複数の文字にもできます (例えば '1<>2<>3'.split('<>')['1', '2', '3'] を返します)。区切り文字を指定して空の文字列を分割すると、 [''] を返します。

str.split() — Python ドキュメント(日本語)

つまり、 split() はオプション引数(非必須な引数)を 2 つ受け取ることができる。その引数を使って、利用者は「区切り文字に何を使うのか」と「最大分割回数」を指定することができる、とのことです。戻り値の型は list です。

いくつかサンプルを見てみましょう。

引数なしで利用する

# 引数を指定しなければ、スペースで分割されます
'Guardians of the Galaxy'.split()
# => ['Guardians', 'of', 'the', 'Galaxy']

# スペースはどれだけ長くてもまとめられます
'Guardians   of   the        Galaxy'.split()
# => ['Guardians', 'of', 'the', 'Galaxy']

# 半角スペースの他にも、改行やタブもスペースとみなされます
'Guardians\tof   \t   the   \n   Galaxy'.split()
# => ['Guardians', 'of', 'the', 'Galaxy']

# その他 Unicode で「空白文字」とされているものもスペースとみなされるので
# 日本語で使われる全角空白( ideographic space / U+3000 )も区切り対象になります
'ガーディアンズ オブ ギャラクシー'.split()
# => ['ガーディアンズ', 'オブ', 'ギャラクシー']

引数を指定して利用する

# 第 1 引数に文字を渡せばそれが区切り文字となります
'文壇の、或老大家が亡くなって、その告別式の終り頃から、雨が降りはじめた。'.split('、')
# => ['文壇の', '或老大家が亡くなって', 'その告別式の終り頃から', '雨が降りはじめた。']

# 第 1 引数に文字列を渡した場合、その文字列がまるごと区切りとして使用されます
# 各文字が区切りになるわけではありません
'文壇の、或老大家が亡くなって、その告別式の終り頃から、雨が降りはじめた。'.split('、。')
# => ['文壇の、或老大家が亡くなって、その告別式の終り頃から、雨が降りはじめた。']

# 半角空白を渡した場合の挙動は、引数を渡さなかった場合と大幅に異なります

# 「区切り対象文字が半角空白だけで、なおかつ、半角空白が連続しない場合」にかぎり結果は同じになりますが・・・
'Guardians of the Galaxy'.split(' ')
# => ['Guardians', 'of', 'the', 'Galaxy']

# 連続する空白は 1 つずつ区切られてしまいます
'Guardians   of   the        Galaxy'.split(' ')
# => ['Guardians', '', '', 'of', '', '', 'the', '', '', '', '', '', '', '', 'Galaxy']

# また、タブや全角などの空白文字では区切られません
'ガーディアンズ オブ ギャラクシー'.split(' ')
# => ['ガーディアンズ\u3000オブ\u3000ギャラクシー']

# 第 2 引数は最大区切り回数です
'文壇の、或老大家が亡くなって、その告別式の終り頃から、雨が降りはじめた。'.split('、', 2)
# => ['文壇の', '或老大家が亡くなって', 'その告別式の終り頃から、雨が降りはじめた。']

# 第 2 引数を渡す必要があるけれど、区切り回数は指定したくない場合には `-1` を渡せば OK です
'文壇の、或老大家が亡くなって、その告別式の終り頃から、雨が降りはじめた。'.split('、', -1)
# => ['文壇の', '或老大家が亡くなって', 'その告別式の終り頃から', '雨が降りはじめた。']

以上です。

以下、よくあるニーズ別に使い方を見てみましょう。

複数の文字で区切りたい

str 型の split() メソッドは複数の区切り文字に対応していません。複数の文字で区切りたい場合は、 2 つのアプローチがあります。

  • split() 前に区切り文字を置換しておく
  • 正規表現ライブラリの split()を使う

split() 前に区切り文字を置換しておく:

('愛のままにわがままに僕は君だけを傷つけない'
  .replace('の', '\n')
  .replace('に', '\n')
  .replace('を', '\n')
  .split('\n'))
# => ['愛', 'まま', 'わがまま', '僕は君だけ', '傷つけない']

文字列に含まれないことが保証されている文字がもしあるなら、対象文字をすべてそれに置換した後に split() をかけることで、複数の文字で区切ることができます。

ただ、ほとんどの場面では次の「正規表現ライブラリの split() 」を使った方法を使うのがよいでしょう。

正規表現ライブラリの split()を使う:

正規表現の関数 split() を使えば複数の区切り文字での分割がシンプルにできます。

import re

re.split('[のにを]', '愛のままにわがままに僕は君だけを傷つけない')
# => ['愛', 'まま', 'わがまま', '僕は君だけ', '傷つけない']

あらゆる空白を区切り文字にしたいが、最大回数は指定したい

str 型の split() に第 1 引数を渡さなかったときの挙動を維持しつつ、最大分割回数は指定したい場合は、第 1 引数に None を指定して第 2 引数を指定します。

'Guardians   of   the        Galaxy'.split(None, 1)
# => ['Guardians', 'of   the        Galaxy']

あるいは、最大分割回数の引数を名前付きで渡しても OK です。

'Guardians   of   the        Galaxy'.split(maxsplit=1)
# => ['Guardians', 'of   the        Galaxy']

文字列を 1 文字ずつ分割したリストを取得したい

文字列を 1 文字ずつ分割したい場合に split() を使うことはできません。 split() を使うと、区切り文字が文字列から除外されてしまうためです。

その用途には、コンストラクタ list() などが使えます。

list('ガーディアンズ オブ ギャラクシー')
# =>
['ガ', 'ー', 'デ', 'ィ', 'ア', 'ン', 'ズ', '\u3000', 'オ', 'ブ', '\u3000', 'ギ', 'ャ', 'ラ', 'ク', 'シ', 'ー']

文字列を改行文字で分割したい

文字列を改行文字で分割したい場合は、多くの場合、 split() よりも、専用のメソッド splitlines() を使うとよいでしょう。

haiku = '''朝顔に
我は飯食う


男哉'''

haiku.splitlines()
# => ['朝顔に', '我は飯食う', '', '', '男哉']


split() だと、連続した改行(空行)がまとめられてしまいます。

haiku = '''朝顔に
我は飯食う


男哉'''

haiku.split()
# => ['朝顔に', '我は飯食う', '男哉']

このあたりはどちらの挙動が望ましいものなのかを先に把握して使い分けるとよいでしょう。

リスト全体を一気に生成せずにイテレータなどを介して各行を取得したい場合などは io.StringIO などを使ってもよいかもしれません。

import io
  
haiku = '''朝顔に
我は飯食う


男哉'''

for line in io.StringIO(haiku):
    print(repr(line))
# =>
# '朝顔に\n'
# '我は飯食う\n'
# '\n'
# '\n'
# '男哉'

str 型の split() メソッドについては以上です。続いて正規表現ライブラリの split() について見ていきましょう。


正規表現ライブラリの split()


正規表現ライブラリにも str 型と似た split() があります。

厳密にいうと、「 split() 関数」と「正規表現オブジェクトの split() メソッド」の 2 つがあります。

import re

# split() 関数
re.split('[のにを]', '愛のままにわがままに僕は君だけを傷つけない')
# => ['愛', 'まま', 'わがまま', '僕は君だけ', '傷つけない']

# 正規表現オブジェクトの split() メソッド
pattern = re.compile('[のにを]')
pattern.split('愛のままにわがままに僕は君だけを傷つけない')
# => ['愛', 'まま', 'わがまま', '僕は君だけ', '傷つけない']

ここでは、 split() 関数の方を使って使い方を見てみましょう。

まず、宣言文は次のようになっています。

def split(pattern, string, maxsplit=0, flags=0):

各引数について説明します。

  • pattern: 区切りに使いたい文字の正規表現パターン。
  • string: 分割対象の文字列。
  • maxsplit: 最大分割回数。 str 型の同名の引数と同じイメージ。
  • flags: 正規表現フラグ。

正規表現フラグには次のものが利用可能です。

  • re.ASCII (re.A)
  • re.DEBUG
  • re.IGNORECASE (re.I)
  • re.LOCALE (re.L)
  • re.MULTILINE (re.M)
  • re.DOTALL (re.S)
  • re.VERBOSE (re.X)

このフラグは Python 3.6 以降では Enum 型の enum.IntFlag のサブクラスである re.RegexFlag のインスタンスとなっているようです。

このフラグは正規表現のオプションを指定するためのものです。正規表現に馴染みのある方はどういうものを意味するのかピンと来るのではないかと思います。詳しく知りたい方は公式のドキュメントにあたってみてください。

re — Python 公式ドキュメント

正規表現ライブラリの split() 関数の docstring は次のようになっています。

import re

print(re.split.__doc__)
# =>
# Split the source string by the occurrences of the pattern,
#     returning a list containing the resulting substrings.  If
#     capturing parentheses are used in pattern, then the text of all
#     groups in the pattern are also returned as part of the resulting
#     list.  If maxsplit is nonzero, at most maxsplit splits occur,
#     and the remainder of the string is returned as the final element
#     of the list.

re.split() — Python 公式ドキュメント

この関数は、文字列を正規表現パターンで分割したリストを返す。正規表現の中でかっこ( () )が使われた場合、それは戻り値のリストに含まれる、とのことです。

サンプルを見てみましょう。

import re

# 指定された正規表現で分割する
re.split('[のにを]', '愛のままにわがままに僕は君だけを傷つけない')
# => ['愛', 'まま', 'わがまま', '僕は君だけ', '傷つけない']

# 改行文字で分割する
haiku = '''朝顔に
我は飯食う


男哉'''

re.split('\n', haiku)
# => ['朝顔に', '我は飯食う', '', '', '男哉']

re.split('\n+', haiku)
# => ['朝顔に', '我は飯食う', '男哉']

# () を使って、区切り文字そのものを戻り値のリストに含める
re.split('(\s+)', 'ガーディアンズ  オブ  ギャラクシー')
# => ['ガーディアンズ', '\u3000\u3000', 'オブ', '\u3000\u3000', 'ギャラクシー']

正規表現オブジェクトの split() メソッドの方は引数が少し異なりますが、使い方はこの split() 関数とほぼ同じです。

宣言部は次のようになっているようです。

regex.split(string, maxsplit=0)

正規表現ライブラリの split() については以上です。

...

ここまで来ると押さえておきたいのが「 str 型の split() と正規表現ライブラリの split() はどのように使い分ければよいのか」という点についてですが、こちらは、次のケースでのみ str 型の split() を使って、その他のケースでは正規表現ライブラリの split() を使う、というのがよいのではないかと思います。

  • Unicode のすべての空白文字で区切りたい。空白が連続する場合はひとつにまとめたい。 → str 型の split() を区切り文字指定なしで利用する。
  • 区切り文字が 1 つだけ。区切り文字が連続する場合は、まとめずに空文字で分割してほしい。 → str 型の split() を区切り文字を指定して利用する。

レアケースな気もしますが、実行時パフォーマンス向上を追求しないといけない場合は、無理やりにでも str 型の split() を使った方がいいケースもあるかもしれません。


以上、文字列操作に便利な Python の split() についてでした。

0 件のコメント: