Pythonを使った文字列の比較

Pythonでは、文字列は文字の並びであり、事実上オブジェクトとしてメモリに格納されます。各オブジェクトは、以下に示すように id() メソッドを用いて識別することができます。Pythonは同じ値を持つオブジェクトをメモリ上で再利用しようとするので、Pythonではオブジェクトの比較も非常に高速に行えます。

$ python
Python 2.7.9 (default, Jun 29 2016, 13:08:31)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a = "abc"
>>> b = "abc"
>>> c = "def"
>>> print (id(a), id(b), id(c))
(139949123041320, 139949123041320, 139949122390576)
>>> quit()


文字列を比較するために、Pythonはいくつかの異なる演算子を提供しています。まず、それらを以下に詳しく説明します。次に、 stringre モジュールの両方について説明します。これらのモジュールには、大文字と小文字を区別しないマッチや不正確なマッチを処理するメソッドが含まれています。3つ目は、複数行の文字列を扱うために、 difflib モジュールが非常に便利であることです。いくつかの例で、その使い方を理解することができます。

== と != 演算子

基本的な比較演算子として、==!= を使いたいと思うことでしょう。これらは整数値や浮動小数点数の場合と全く同じように動作します。演算子 == は、完全に一致する場合は True を返し、そうでない場合は False を返します。これに対して != 演算子は、マッチしない場合は True を返し、マッチしない場合は False を返します。リスト1がこれを示しています。

for` ループの中で、スイスの都市名 “Lausanne” を含む文字列と、他の場所のリストからのエントリーを比較し、比較結果を標準出力に出力しています。

リスト1:

# define strings
listOfPlaces = ["Berlin", "Paris", "Lausanne"]
currentCity = "Lausanne"


for place in listOfPlaces:
    print ("comparing %s with %s: %s" % (place, currentCity, place == currentCity))


上記のPythonスクリプトを実行すると、次のような出力が得られます。

$ python3 comparing-strings.py
comparing Berlin with Lausanne: False
comparing Paris with Lausanne: False
comparing Lausanne with Lausanne: True


== と is 演算子

Python には 2 つの比較演算子 ==is があります。一見、同じように見えますが、実はそうではありません。== は、2つの変数を実際の値に基づいて比較します。それに対して、 is 演算子はオブジェクト ID に基づいて 2 つの変数を比較し、2 つの変数が同じオブジェクトを参照している場合は True を返します。

次の例では、整数値を持つ3つの変数について、その様子を示しています。2つの変数 ab は同じ値を持ち、Pythonはメモリの使用量を最小限にするために、同じオブジェクトを参照しています。

>>> a = 1
>>> b = 1
>>> c = 2
>>> a is b
True
>>> a is c
False
>>> id(a)
10771520
>>> id(b)
10771520


値が変わるとすぐに、Python はオブジェクトを再定義して変数に代入します。次のコードでは、 b が 2 という値を取得し、その後 bc は同じオブジェクトを参照するようになります。

>>> b = 2
>>> id(b)
10771552
>>> id(c)
10771552


経験則から言うと、intのような不変の型を比較するときは == を使い、オブジェクトを比較するときは is を使うのがよいでしょう。

その他の比較演算子

辞書順の比較を行うには、比較演算子 <, >, <=, >= を使用することができます。比較そのものは一文字ずつ行われます。その順番は、アルファベットの文字の順番に依存します。この順序は、Pythonのコードを実行するときにあなたのマシンで使用されている文字テーブルに依存します。

この順序は大文字と小文字を区別することに注意してください。ラテンアルファベットの例として、”Bus “は “bus “よりも前に来ます。リスト 2 は、これらの比較演算子が実際にどのように機能するかを示しています。

リスト2:

# define the strings
listOfPlaces = ["Berlin", "Paris", "Lausanne"]
currentCity = "Lausanne"


for place in listOfPlaces:
    if place < currentCity:
            print ("%s comes before %s" % (place, currentCity))
    elif place > currentCity:
            print ("%s comes after %s" % (place, currentCity))
    else:
            print ("%s is similar to %s" % (place, currentCity))


上記のPythonスクリプトを実行すると、次のような出力が得られます。

$ python3 comparing-strings-order.py
Berlin comes before Lausanne
Paris comes after Lausanne
Lausanne is similar to Lausanne


大文字・小文字を区別しない比較

これまでの例では、文字列間の完全一致に焦点を当てました。大文字小文字を区別しない比較を可能にするために、Pythonは upper()lower() といった特別な文字列メソッドを提供しています。これらのメソッドは、文字列オブジェクトのメソッドとして直接利用することができます。

upper()は文字列全体を大文字に変換し、lower()は小文字に変換します。リスト1に基づき、次のリストではlower()` メソッドをどのように使うかを示します。

リスト3:

# using the == operator
listOfPlaces = ["Berlin", "Paris", "Lausanne"]
currentCity = "lausANne"


for place in listOfPlaces:
    print ("comparing %s with %s: %s" % (place, currentCity, place.lower() == currentCity.lower()))


出力は以下のようになります。

$ python3 comparing-strings-case-insensitive.py
comparing Berlin with lausANne: False
comparing Paris with lausANne: False
comparing Lausanne with lausANne: True


正規表現の使用

正規表現(略して「レジェックス」)は、特定の文字のパターンを定義するものです。このトピックについては、Jeffrey FriedlがMastering Regular Expressionsという素晴らしい本を書いているので、そちらを強くお勧めします。

Pythonでこの仕組みを利用するには、まずreモジュールをインポートし、次に特定のパターンを定義します。繰り返しになりますが、次の例はリスト1に基づいています。検索パターンは「bay」にマッチし、小文字か大文字のどちらかで始まっています。正確には、以下のPythonコードは、文字列の先頭、中間、末尾のどの位置であっても、検索パターンが出現する文字列をすべて見つけます。

リスト4:

# import the additional module
import re


# define list of places
listOfPlaces = ["Bayswater", "Table Bay", "Bejing", "Bombay"]


# define search string
pattern = re.compile("[Bb]ay")


for place in listOfPlaces:
    if pattern.search(place):
        print ("%s matches the search pattern" % place)


出力は以下のとおりで、地名リストから「Bayswater」、「Table Bay」、「Bombay」にマッチしています。

$ python3 comparing-strings-re.py
Bayswater matches the search pattern
Table Bay matches the search pattern
Bombay matches the search pattern


複数行とリストの比較

今までのところ、私たちの比較はいくつかの単語についてだけでした。Python の difflib モジュールを使うと、複数行の文字列や単語全体のリストを比較する方法も提供されます。出力はdiffツールの様々なフォーマットに従って設定することができます。

次の例 (リスト 5) では、2つの複数行の文字列を1行ずつ比較して、削除と追加を表示しています。12行目で Differ オブジェクトを初期化した後、15行目で compare() メソッドを用いて比較を行います。結果は標準出力に表示されます (18行目)。

リスト5:

# import the additional module
import difflib

# define original text
# taken from: https://en.wikipedia.org/wiki/Internet_Information_Services
original = ["About the IIS", "", "IIS 8.5 has several improvements related", "to performance in large-scale scenarios, such", "as those used by commercial hosting providers and Microsoft's", "own cloud offerings."]


# define modified text
edited = ["About the IIS", "", "It has several improvements related", "to performance in large-scale scenarios."]


# initiate the Differ object
d = difflib.Differ()

# calculate the difference between the two texts
diff = d.compare(original, edited)

# output the result
print ('
'.join(diff))


このスクリプトを実行すると、以下のような出力が得られます。削除された行は - 記号で表示され、追加された行は + 記号で始まります。さらに、変更のある行はクエスチョンマークで始まります。変更箇所は ^ 記号で示され、その位置に表示されます。記号がない行はそのままです。

$ python comparing-strings-difflib.py
  About the IIS

- IIS 8.5 has several improvements related
?  ^^^^^^


+ It has several improvements related
?  ^


- to performance in large-scale scenarios, such
?                                        ^^^^^^


+ to performance in large-scale scenarios.
?                                        ^


- as those used by commercial hosting providers and Microsoft's
- own cloud offerings.


結論

この記事では、Pythonで文字列を比較するさまざまな方法について学びました。この概要が、開発者の皆さんの効率的なプログラミングに役立てば幸いです。

謝辞

本論文の作成にあたり、Mandy Neumeyerのサポートに感謝したい。

タイトルとURLをコピーしました