一覧PG-09-3 · 文字列の処理

正規表現による抽出と入力検証

利用者名と通信量を持つログへ同じパターンを適用します。グループ、検索範囲、数字の文字種、数値の範囲を順に確認し、形式と値の検査を分けます。

説明

正規表現は文字列の形を指定し、一致した部分から値を取り出すために使えます。途中に一致があることと、入力全体が指定形式に従うことは別です。

利用者名と通信量を持つログへ同じパターンを適用します。グループ、検索範囲、数字の文字種、数値の範囲を順に確認し、形式と値の検査を分けます。

1抽出する形式を用意

user=英小文字、空白、bytes=十進数字という形式を指定します。括弧で利用者名と数値をグループへ保持します。

import re; pattern = re.compile(r"user=([a-z]+) bytes=([0-9]+)")文字種と反復を指定; line = "user=aya bytes=120"; match = pattern.fullmatch(line)一行全体を照合; print(match.group(0), match.groups(), match.span())全体・抽出値・位置

group(0)は一致全体、groups()は括弧に対応するayaと120です。spanは開始0・終了18で、終了位置は一致に含めません。

2抽出した数字は文字列

第二グループの型を調べ、数量として計算するためintへ変換します。

size_text = match.group(2)数字部分の文字列; size = int(size_text)数量へ変換; print(type(size_text).__name__, size, size + 8)型と計算結果

正規表現が数字に一致しても、取り出した値はstrです。intへ変換すると120として加算でき、128になります。

3途中の一致と全体の一致

行頭と末尾に余分な文字を加え、search・match・fullmatchを比較します。

wrapped = "INFO " + line + " extra"余分な前後の文字; print(pattern.search(wrapped).span()途中も探す); print(pattern.match(wrapped)先頭から一致を試す); print(pattern.fullmatch(wrapped)全体の一致を要求)

searchは位置5から一致を見つけますが、matchは先頭INFOで不一致、fullmatchも余分な部分があるため不一致です。不一致を表す戻り値はNoneです。

4先頭が一致しても末尾が残る場合

今度は正しい形式が先頭にあり、末尾だけ余分な入力です。

trailing = line + " extra"末尾に余分な内容; print(pattern.match(trailing).group(0)一致した部分だけ取得); print(pattern.fullmatch(trailing)余りも許さない検査)

matchは正しい部分だけを返し、extraを消費しません。入力形式を検証したいときに、先頭の一致だけを成功扱いすると余分な入力を見逃します。

5数字の範囲はパターンで指定する

全角の120に対し、Unicodeの数字を扱う\dとASCIIの0〜9を比較します。

print(bool(re.fullmatch(r"\d+", "120")Unicodeの十進数字)); print(bool(re.fullmatch(r"[0-9]+", "120")ASCIIの数字だけ)); print(bool(re.fullmatch(r"\d+", "120", flags=re.ASCIIASCIIとして解釈)))

通常のstrパターンの\dは全角数字にも一致します。[0-9]またはre.ASCIIを使うと、この入力では不一致です。プロトコルがASCII数字だけを許すなら、見た目が数字かどうかではなく文字種の条件を明示します。

6名前付きグループで値を取り出す

数字のグループに名前を付け、後続処理で位置番号へ依存しない形にします。

named = re.compile(r"user=(?P<user>[a-z]+)利用者名グループ bytes=(?P<size>[0-9]+)通信量グループ"); found = named.fullmatch(line)同じ行を照合; print(found.groupdict())名前と値の辞書; print(found.group("size"))名前で抽出

辞書としてuserとsizeを取得できます。括弧を追加して番号が変わる場合に備え、役割が分かるグループ名を使えます。

7形式が合っても数値の上限は別

bytesの上限を1000とする仕様を考え、120・1200・末尾文字ありを検査します。

samples = [line, "user=aya bytes=1200", "user=aya bytes=120x"]正常・範囲外・形式違い; print([(s, (m := named.fullmatch(s)) is not None一致結果を保持して検査, m is not None and int(m.group("size")) <= 1000一致した場合だけ数量の上限を検査) for s in samples])

1200は形式には一致しますが、上限の条件に違反します。120xは形式そのものが不一致です。先に一致の有無を確認するため、不一致のNoneへgroupを呼び出していません。:=は一致結果をmへ保持する代入式です。

まとめ

部分抽出と入力全体の形式検査を、適切な一致関数で使い分けます。

一致した文字列を数量へ変換し、値の範囲は別の条件で検査します。

操作履歴と解説を振り返れます。

Python 3

1
import re; pattern = re.compile(r"user=([a-z]+) bytes=([0-9]+)"); line = "user=aya bytes=120"; match = pattern.fullmatch(line); print(match.group(0), match.groups(), match.span())
user=aya bytes=120 ('aya', '120') (0, 18)
2
size_text = match.group(2); size = int(size_text); print(type(size_text).__name__, size, size + 8)
str 120 128
3
wrapped = "INFO " + line + " extra"; print(pattern.search(wrapped).span()); print(pattern.match(wrapped)); print(pattern.fullmatch(wrapped))
(5, 23)
None
None
4
trailing = line + " extra"; print(pattern.match(trailing).group(0)); print(pattern.fullmatch(trailing))
user=aya bytes=120
None
5
print(bool(re.fullmatch(r"\d+", "120"))); print(bool(re.fullmatch(r"[0-9]+", "120"))); print(bool(re.fullmatch(r"\d+", "120", flags=re.ASCII)))
True
False
False
6
named = re.compile(r"user=(?P<user>[a-z]+) bytes=(?P<size>[0-9]+)"); found = named.fullmatch(line); print(found.groupdict()); print(found.group("size"))
{'user': 'aya', 'size': '120'}
120
7
samples = [line, "user=aya bytes=1200", "user=aya bytes=120x"]; print([(s, (m := named.fullmatch(s)) is not None, m is not None and int(m.group("size")) <= 1000) for s in samples])
[('user=aya bytes=120', True, True), ('user=aya bytes=1200', True, False), ('user=aya bytes=120x', False, False)]

演習完了