Pythonで文字列を比較(完全一致、部分一致、大小関係など)
Pythonで、文字列同士を比較して完全一致や部分一致、前方一致、後方一致などを判定する方法について説明する。
文字列を検索して一致する部分の位置を取得したい場合は以下の記事を参照。
完全一致(等価): ==, !=
数値などと同じく、2つの文字列が完全一致(等価)かどうかの判定には==
演算子を使う。等価だとTrue
、等価でないとFalse
が返される。
print('abc' == 'abc')
# True
print('abc' == 'xyz')
# False
大文字と小文字は区別される。以降の演算子やメソッドでも同様。大文字と小文字を区別せずに比較する方法は後述。
print('abc' == 'ABC')
# False
!=
は、等価でないとTrue
、等価だとFalse
を返す。
print('abc' != 'xyz')
# True
print('abc' != 'abc')
# False
部分一致: in, not in
部分一致、すなわち、ある文字列の中にもう一方の文字列が含まれているかどうかの判定にはin
演算子を使う。
x in y
としたとき、x
がy
に含まれている(x
がy
の部分文字列である)とTrue
、含まれていないとFalse
が返される。x
の各文字がバラバラにy
に含まれている場合はFalse
。
print('bbb' in 'aaa-bbb-ccc')
# True
print('xxx' in 'aaa-bbb-ccc')
# False
print('abc' in 'aaa-bbb-ccc')
# False
not in
は、含まれていないとTrue
、含まれているとFalse
を返す。
print('xxx' not in 'aaa-bbb-ccc')
# True
print('bbb' not in 'aaa-bbb-ccc')
# False
in
, not in
はリストなどの要素の判定にも使われる。if文の条件式として使う例などの詳細は以下の記事を参照。
前方一致・後方一致(先頭・末尾): startswith(), endswith()
前方一致、すなわち、ある文字列の先頭が指定した文字列から始まっているかを判定するには文字列のメソッドstartswith()
を使う。
s = 'aaa-bbb-ccc'
print(s.startswith('aaa'))
# True
print(s.startswith('bbb'))
# False
引数には文字列のタプルを指定することも可能。先頭がタプルの要素のいずれかの文字列に一致するとTrue
、どれにも一致しないとFalse
が返される。タプルではなくリストだとエラーとなるので注意。
print(s.startswith(('aaa', 'bbb', 'ccc')))
# True
print(s.startswith(('xxx', 'yyy', 'zzz')))
# False
# print(s.startswith(['a', 'b', 'c']))
# TypeError: startswith first arg must be str or a tuple of str, not list
後方一致、すなわち、ある文字列の末尾が指定した文字列で終わっているかを判定するには文字列のメソッドendswith()
を使う。使い方はstartswith()
と同じ。
print(s.endswith('ccc'))
# True
print(s.endswith('bbb'))
# False
print(s.endswith(('aaa', 'bbb', 'ccc')))
# True
文字列の大小関係(順序)
文字列も数値などと同様に<
, <=
, >
, >=
演算子で比較できる。辞書のように、1文字目が同じなら2文字目、3文字目...と順番に比較される。
print('a' < 'b')
# True
print('aa' < 'ab')
# True
print('abc' < 'abcd')
# True
Python3では文字列str
はUnicodeであり、文字列の大小関係(順序)は文字のUnicodeコードポイント(文字コード)で判定される。
文字のUnicodeコードポイントは組み込み関数ord()
で取得可能。
print(ord('a'))
# 97
print(ord('b'))
# 98
UnicodeコードポイントはUnicodeコンソーシアムのサイトやWikipediaでも確認できる。
例えば最初の方のUnicode一覧は以下のWikipediaのページを参照。
アルファベットはまとまって配置されているが、大文字のほうが小文字よりもコードポイントが小さい(若い)。
print('Z' < 'a')
# True
print(ord('Z'))
# 90
ひらがな、カタカナもそれぞれまとまって配置されている。基本的には五十音順の通りで、ひらがなのほうがカタカナよりもコードポイントが小さい(若い)。
print('あ' < 'い')
# True
print(ord('あ'))
# 12354
print(ord('い'))
# 12356
print('ん' < 'ア')
# True
print(ord('ん'))
# 12435
print(ord('ア'))
# 12450
アルファベットやひらがな、カタカナは分かりやすい順番に(A→Z、あ→ん、ア→ン)に並んでいるが、漢字はバラバラ。読み仮名順などに並んでいるわけではない。
人間のイメージどおりの大小関係になるとは限らないので注意。
print('乙' < '亜')
# True
print(ord('乙'))
# 20057
print(ord('亜'))
# 20124
print('七' < '三')
# True
print(ord('七'))
# 19971
print(ord('三'))
# 19977
リストのメソッドsort()
や組み込み関数sorted()
で文字列のリストをソートする場合も、Unicodeコードポイントを元に順序が決まる。
print(sorted(['aaa', 'abc', 'Abc', 'ABC']))
# ['ABC', 'Abc', 'aaa', 'abc']
なお、組み込み関数sorted()
に文字列を渡すと、ソートされた各文字が要素となるリストが返される。この場合も、Unicodeコードポイントを元に順序が決まる。以下の例のように、漢数字などはむしろバラバラになる。
print(sorted('一二三四五六七八九十'))
# ['一', '七', '三', '九', '二', '五', '八', '六', '十', '四']
任意の順番にソートしたい場合は以下の記事を参照。
大文字小文字を区別せずに比較
ここまで説明した演算子やメソッドはすべて大文字小文字を区別して処理する。
大文字小文字を区別せずに比較したい場合は、両方の文字列を大文字または小文字に変換して統一すればよい。文字列をすべて大文字に変換するにはupper()
、小文字に変換するにはlower()
を使う。
s1 = 'abc'
s2 = 'ABC'
print(s1 == s2)
# False
print(s1.lower() == s2.lower())
# True
なお、日本語のような大文字小文字の区別がない文字を含む文字列にupper()
やlower()
メソッドを適用しても問題ない。大文字小文字の区別がない文字はそのまま変化しない(無視される)。
s = 'Abcあいうえお'
print(s.lower())
# abcあいうえお
print(s.upper())
# ABCあいうえお
正規表現パターンにマッチ: re.search(), re.fullmatch()
正規表現を利用するとより柔軟な処理が可能。
標準ライブラリのreモジュールを使う。詳細は以下の記事を参照。reモジュールの正規表現シンタックスの注意点などにも触れている。
re.search()
部分一致・前方一致・後方一致にはre.search()
を使う。第一引数に正規表現パターンの文字列、第二引数に対象の文字列を指定する。
なお、前方一致にはre.match()
も使えるがここでは触れない。詳細は前掲のreモジュールの記事を参照。
正規表現パターンには様々なメタ文字(特殊文字)が利用できるが、単に文字列をそのまま指定することも可能。その文字列が含まれているとマッチオブジェクト、含まれていないとNone
が返される。
import re
s = 'aaa-AAA-123'
print(re.search('aaa', s))
# <re.Match object; span=(0, 3), match='aaa'>
print(re.search('xxx', s))
# None
マッチオブジェクトは常にTrue
と判定されるので、if文で条件式として使いたい場合はre.search()
またはその返り値をそのまま使えばよい。詳細は以下の記事を参照。マッチオブジェクトのメソッドでマッチした部分の文字列や位置を取得することもできる。
メタ文字^
は文字列の先頭、$
は文字列の末尾にマッチする。これらを使うと前方一致・後方一致が実現できる。
print(re.search('^aaa', s))
# <re.Match object; span=(0, 3), match='aaa'>
print(re.search('^123', s))
# None
print(re.search('aaa$', s))
# None
print(re.search('123$', s))
# <re.Match object; span=(8, 11), match='123'>
そのほか様々なメタ文字や特殊シーケンスが利用可能。例えば、[A-Z]
は大文字のアルファベットいずれか1文字を表し、+
は直前のパターンが1回以上繰り返されることを表す。したがって、[A-Z]+
はアルファベット大文字が1文字以上連続している部分文字列にマッチする。
print(re.search('[A-Z]+', s))
# <re.Match object; span=(4, 7), match='AAA'>
このように、正規表現パターンを活用すると特定の文字列ではなくある条件を満たす文字列との一致を判定できる。
ワイルドカード的な指定など、正規表現パターンを活用した基本的な例を以下の記事で紹介しているので参考にされたい。
re.fullmatch()
文字列全体が正規表現パターンにマッチしているかどうかを判定するにはre.fullmatch()
を使う。一部がマッチしても、マッチしていない部分があるとNone
が返される。
s = '012-3456-7890'
print(re.fullmatch(r'\d{3}-\d{4}-\d{4}', s))
# <re.Match object; span=(0, 13), match='012-3456-7890'>
s = 'tel: 012-3456-7890'
print(re.fullmatch(r'\d{3}-\d{4}-\d{4}', s))
# None
ここで、\d
は数字、{n}
はn
回の繰り返しを表す。\d
のように正規表現の特殊シーケンスではバックスラッシュ\
が使われるので、''
や""
の前にr
を付けてエスケープシーケンスを無効化するraw文字列を使うと便利。
re.IGNORECASE
re.search()
やre.fullmatch()
などの関数の引数flags
にre.IGNORECASE
を指定することで、大文字小文字を区別せずに比較できる。
s = 'ABC'
print(re.search('abc', s))
# None
print(re.search('abc', s, re.IGNORECASE))
# <re.Match object; span=(0, 3), match='ABC'>