【正規表現】Pythonのsplitで区切り文字を返却リスト要素に含める方法



splitは指定した文字で目的の文字列を区切って、区切られたそれぞれの要素をリストとして返却するメソッドです。

似たようなメソッドはほかの言語にも存在します。

「splitで区切り文字列を返却要素に含めたい」と思って色々調べたのでメモ書き残し。

splitの使い方

# 例えばURLのパラメータなんかの場合
params = 'post=3332&action=edit&classic-editor__forget'
params.split('&')

>>>['post=3332', 'action=edit', 'classic-editor__forget']

&で区切られたそれぞれの文字列が返却されます。
区切りとなる文字「&」は消え去って返ってくる事はありません。
これが基本です。

目的:splitの区切り文字列を返却要素に含める

文字列操作をしていると区切り文字列も返却要素内に欲しいパターンがあります。

【参考サイト】

# 普通にやると・・・
string = "吾輩は猫である。名前はまだない。"
re.split("。", string)
# 区切り文字の"。"は含まれない
>>>['吾輩は猫である', '名前はまだない', '']


# 区切り文字を()で囲む
re.split("(。)", string) # 半角丸括弧でくくる
>>>['吾輩は猫である', '。', '名前はまだない', '。', '']

含まれるには含まれた。

が、やりたいのはこうじゃない。

こうなってほしい。

['吾輩は猫である。', '名前はまだない。', '']

結論:splitでは無理

結論から言うとsplitでは無理っぽいという着地です(笑)
もちろん、ごちゃごちゃいじくり回せばいくらでもやり方は思いつくのですが、短く簡潔に表現したいですよね。

区切り文字を前の要素に含める形で再現

ってことで、正規表現モジュール[re]をインポートします。

import re
re.findall(".*?。", s2)
>>>['吾輩は猫である。', '名前はまだない。']

なるほど。それっぽい。
まぁこれで事足りる場合も多いかと思います。

正規表現なら「区切りがない」文字列にも対応できる

そもそも何で区切ればいいのか分からないパターンでも、正規表現なら抜き出せます。例えばこんな文字列です。

strings = "【注意事項】使用感有【シリアル】LX5843VG【付属品】ネームタグ"

# 何とかしてこう出力したい。
['【注意事項】使用感有','【シリアル】LX5843VG','【付属品】ネームタグ']

これは難しいですよね・・・。そもそも何を区切り文字として指定すればいいのかわかりません。

勿論、splitを使ってやろうと思えば何とかできます。

['【' + s for s in strings.split('【') if s]

>>>['【注意事項】使用感有', '【シリアル】LX5843VG', '【付属品】ネームタグ']

出来るには出来ますが、ダサいですね。
一回外した"【"を再度付け直している辺りが非常にダサいです。美しくない。

re.findallで美しく華麗に抜き出す

re.findall("【.*?(?=【|$)", strings)

>>>['【注意事項】使用感有', '【シリアル】LX5843VG', '【付属品】ネームタグ']

どうです?カッコよくないですか?

ポイントは"【" から次の"【"までをひとまとまりと考えて、カッコ内抜出の正規表現を応用します。

【参考サイト】正規表現:括弧の中身だけをマッチする表現

カッコを含めて一致するパターンと、カッコ内部にだけ一致するパターンを応用して組み合わせています。

正規表現の内容を解説

部分的に解説します。

【】に囲まれた文字列に【】を含めてマッチするパターン

pattern = "【.*?"
# 【 から始まる何らかの文字列。
# ?の直後に書いた文字列が出現した地点までの間を「マッチした」と見なします。

# つまりこのように書くと【】に囲まれた文字列を抜き出します。
re.findall("【.*?】", strings)

>>> ['【注意事項】', '【シリアル】', '【付属品】']
【】自体を含めて「マッチした」と見なします。

【】に囲まれた内部の文字列だけにマッチするパターン

pattern = "(?=【|$)"
# "【" または $(文字列の終端)に当たる直前までの文字列を「マッチした」と見なす。

肯定先読みという表現です。

pattern = "(?<=【)"
# "【"に行き当たった直後の文字列を「マッチした」と見なす

よく似ていますが、こちらは肯定戻り読みという表現です。

これらを組み合わせると【】内部だけを抜き出す表現になります。

# 【】の内部の文字列だけにマッチします。
re.findall("(?<=【).*?(?=】)", strings)

>>>['注意事項', 'シリアル', '付属品']
正規表現部品解説
肯定戻り読み
  •  (?<=【)

"【"より後で出現した正規表現部分をマッチ部分と見なす。

最短一致(抜き出される本体)
  •  .*?

任意の一文字、0回以上の繰り返し、
?の直後の文字列までをマッチ部分とみなす。

肯定先読み
  •  (?=】)

"】"より前に出現した正規表現部分をマッチ部分と見なす。

まとめ

splitでやるよりもスマートにできたと思います。

正規表現自体はなんだかややこしくて最初のうちは上手く書くのは難しいですが、慣れてくればあらゆる文字列操作に対応できるようになるパワフルなメソッドですね。

しかも基本的に正規表現自体は言語を問わず使えるので一度覚えるとどの言語に移ってもそのままの知識で利用できるのがイイところです。

created by Rinker
¥5,280 (2022/11/27 06:46:43時点 Amazon調べ-詳細)