Python入門(3) 文字列操作

大抵のプログラミングにおいて、文字列操作処理は多くの局面で中核的部分を占める。 オブジェクト指向プログラミング言語(OOPL: Object Oriented Programming Programming Language)であるPythonには、そのためのメソッド(method)が用意されている。

あるオブジェクト obj に対して、オブジェクトが属するクラス(class)で定義されているメソッド operation() を適用するには obj.operation() とオブジェクトの後にピリオド (.)をつけて作用させる。

ここでは、文字列操作に関する膨大なメソッドからほんの幾つかを紹介しておく。

文字列操作のメソッド

文字変数を astring とする。

文字列のメソッド
method名使用例意味
lowerastring.lower()文字列の全ての文字を小文字にして返す。astringは変わらない。
upperastring.upper()文字列の全ての文字を大文字にして返す。astringは変わらない。
countastring.count(item)文字列 astring 内のitem文字列(文字)の登場回数を返す。
findastring.find(item)文字列 astring 内のitem文字列(文字)が初めて登場する位置を返す。
rfindastring.rfind(item)文字列 astring 内のitem文字列(文字)が最後に見つかった位置を返す
startswithastring.startswith(item)文字列 astring が文字列item文字列(文字)で始めるかどうかでTrue または False を返す
endswithastring.endswith(item)文字列 astring が文字列item文字列(文字)で終わるかどうかでTrue または False を返す
centerastring.center(w)文字列を長さ w のフィールド内の中央に配置して返す。文字列内容は変わらない。
rjustastring.rjust(w)文字列を長さ w のフィールド内に右寄せして返す。文字列内容は変わらない。
ljustastring.ljust(w)文字列を長さ w のフィールド内に左寄せして返す。文字列内容は変わらない。
replaceastring.replace(before, after)文字列内の文字列 before を検索し、文字列 after にすべて置き換えた結果を返す。astringは変わらない。
splitastring.split(st)文字列を指定した文字列 st を区切りとして部分文字列に分割し、部分文字列のリストを 返す(文字列stは部分文字列には現れない)。 文字列 stを明示せず省略したときは空白1文字" "で分割される。
joinsep.join(alist) 文字列を要素とするリスト alist の要素を文字列 sep 挟み込んで連接した文字列を返す。 sep が ""(空文字列)のときはリスト alist の要素をそのまま並べた文字列を、sep が 空白(" ") としたときは空白を挟んで並べた文字列が返る。

文字列へメソッドは、以下で確認されるように元の文字列内容を変化させない。 一方、Python入門(6) リストで紹介されるリストへのメソッドはリスト内容を変化させてしまう。 このようなリストの性質を変更可能 (mutability)という。 文字列は mutableではない。

文字列を大文字または小文字に変換

文字列 "aBcDeFgHiJ" をすべて小文字に変換するメソッド lower( )、すべて大文字に変換するメソッド upper( ) を適用する。

Python shellで確認してみよう。

>>> "aBcDeFgHiJ".lower()
'abcdefghij'
>>> "aBcDeFgHiJ".upper()
'ABCDEFGHIJ'
演習: Python shellに英数文字列を入力して、メソッド lower() および upper() の結果を確かめなさい。

メソッドの変数に対する効果

もちろん、文字列変数にメソッドを適用することも可能である。

>>> st = "pRsTuVwXy"
>>> st.lower()
'prstuvwxy'
>>> st.upper()
'PRSTUVWXY'

重要なことは、変数にメソッドを適用しても、変数に格納されている値は影響されないことだ。 変数に格納された値を書き換えるのは、メソッド結果を再割り当てする。

>>> st= "pRsTuVwXy"
>>> st.upper()
>>> 'PRSTUVWXY'
>>> st
>>> 'pRsTuVwXy'
st = st.upper()
>>> st
>>> 'PRSTUVWXY'
演習: スクリプトを実行して、変数にメソッド lower() および upper() を適用しても、その変数値には影響を与えないことを確認しなさい。

文字列パタンで検索して、最初のインデックスを求める find

与えられた文字列に対して、指定した文字列パターンに一致する文字列中の最小インデックスを返すメソッドが find( ) である。

>>> st = "abcdefabcghi"
>>> st.find("cde")
>>> 2
>>> st.find("abc")
>>> 0
>>> st.find("abx")
>>> -1

文字列 "abcdefabcghi" が割り当てられた文字列変数 st 内に、文字列パターン "cde" は st の2番目から始まり、文字列パターン "abc" は 0 番目から始まり、文字列パターン "abx" は st には存在しない。 メソッド find は検索に失敗すると -1 を返す。 パターン "abc" は st 内の後方位置にもあるのだが、メソッド find は検索して最初に見つかったインデックス(最小インデックス)を返すのである。

文字列インデックスが 0 から始まっていることに注意されたい。 st.find("abc") は文字列 st の先頭インデックスに見つかり、0 を返している。 Pythonでは文字列位置は0番目から始まるのである(多くのプログラム言語でも0番目から始まる)。 実際、リストから要素を取り出す記法 [ ] を使うと次のようになっている。

>>> st[0]
'a'
>>> st[1]
'b'
>>> st[2]
'c'
>>> st[3]
'd'
>>> st[len(st) - 1]
'i'

最後に、文字列の長さを返す関数 len() を使った(関数 len は文字列のメソッドではない)。

文字列パタンで検索して、 最後のインデックスを求める rfind

メソット find と同じように文字列から文字列パタンを探すが、最後に見つかったインデックスを返するメソットが rfin である。 メソット find と同じように文字列から文字列パタンを探すが、最後に見つかったインデックスを返するメソットが rfind である。

>>> st = "abcdefabcghi"
>>> st.rfind("abc")
6
>>> st.rfind("abd")
-1

指定した文字列で始まるかどうか startswith

関連するメソッドには、文字列が指定する文字列パターンで始まるかを調べるメソッド startswith がある。 真理定数 True または False を返す。

>>> "abcdefabcghi".startswith("abc")
>>> True
>>> "abcdefabcghi".startswith("bcd")
>>> False

指定した文字列で終了するかどうか endswith

同様に、文字列が指定する文字列パターンで終了するかを調べるメソッド endswith がある。 真理定数 True または False を返す。

>>> "abcdefabcghi".endswith("cghi")
>>> True
>>> "abcdefabcghi".startswith("cgh")
>>> False
演習: 長い半角英数字と空白からなる文字列を用意しておき、キーボードから入力した任意文字列パタンを見つけるスクリプト find_substr.py を書いて実行してみなさい。
(ヒント)かなり長い文字列でもよい。次は LincolinのGettysburg演説の後半を検索対象変数 st に割り当てた。
st= " But, in a larger sense, we can not dedicate, we can not consecrate, we can not hallow this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom—and that government of the people, by the people, for the people, shall not perish from the earth."

文字列のスライス(文字列の一部を切り出す)

与えられた文字列のインデックスを記法 [from: until: k] によって指定することで、指定範囲の文字列を切り出すことができる。

>>> st = "abcdefabcghi.py"
>>> st[3:10]
'defabcg'

Pythonのインデックス記法 インデックスを記法 [from:until] は強力で、引数を省略することができる。 省略されたインデックスは先頭あるいは末尾が入って解釈される。

>>> st[:]
'abcdefabcghi.py'
>>> st[6:]
'abcghi.py'
>>> st[:6]
'abcdef'

興味深いのは、負インデックスを与えて、文字列の末尾から先頭に向かって切り出すことができることだ。 指定した拡張子を見つける場合に重宝するはずだ。

>>> st = "abcdefabcghi.py"
>>> st[-2:]
'py'
>>> "readme.txt"[-3:]
'txt'

この機能を使うと、絶対指定したディレクトリ(フォルダ)内にある 拡張子 .py が付いたファイルまたはディレクトリを一覧するスクリプト dir_ext_list.py は次のように書ける。

import os # osモジュールのインポート

# パス内の全てのファイルとディレクトリを要素とするリストを返す
files = os.listdir('目的のディレクトリへの絶対パス')

for file in files:
    if file[-3:] == ".py":
        print(file)
  1. 4行目で、OSごとに定義されたファイル操作するmoduleをimportし、
  2. 7行目で、os.listdir('絶対ディレクトリパス') によって、そのディレクトリ内にあるファイルおよびディレクトリに一覧をリストとして取得し、
  3. 9行目で、繰り返し文 for でそのリスト項目 file について、
  4. 10行目で条件文 if を使ってfileの後ろ3文字が '.py' と等しいかを判定し
  5. 真であれば、11行目で file をプリントする。

'目的のディレクトリへの絶対パス'とは、たとえばディレクトリ(フォルダ) python の場所を次のように指定することである。 ターミナルで目的フォルダに移動して(Windowsでは PowerShellを使って)コマンド pwd で表示できる。

演習: Pythonスクリプトがあるフォルダを絶対指定してスクリプト dir_ext_list.py を実行してみなさい。 さらに、特定の拡張子を持つファイルを書き出すように修正し、実行してみなさい。

文字列のスライス定義

このように、文字列の特定部分の切り出しをスライス(slice)という。 この正確な定義を与えておこう。

文字列 mystring のスライス表記 mystring[i: j: k] とは

i, j, k を省略すると先頭 0 番目と末尾 len(mystring) -1 番目の場所,および k=1 と見なされ、 mystring[:] および mystring[: :] は mystring それ自身を表す。

演習: これらの文字列のスライス表記をたとえば以下のようにして確かめてみなさい。
>> mystring = 'abcdefghijklmnopqrstuvwxyz'
>>> len(mystring)
26
>>> mystring[0:len(mystring)]
'abcdefghijklmnopqrstuvwxyz'
>>> mystring[::-1]
'zyxwvutsrqponmlkjihgfedcba'
>> mystring[1:25:4]
>>> mystring[1:25:4]
'bfjnrv'
>>> mystring[5::-2]
'fdb'

文字列の反転

Pythonでは、文字列を反転(reverse:先頭からの要素並び順が反転によって末尾からの遡った要素並び順と一致)するメソッドは提供されていない。 ただし、リスト mylist を反転するためのメソッド reverse があり、mylist.reverse() によって、mylist の要素並びが反転する。

文字列 mystring の反転は格別な関数を定義する必要はなく、文字列の後に [: :-1] とスライス表記するだけでよい。 ただし、次でわかるように、そのように反転表示が返っても文字列そのものは不変なままだ。

>> mystring = 'apple'
>> mystring[::-1]
'elppa'
>> mystring
'apple'

文字列の置き換え

文字列内の指定した検索文字列を検索し、別の文字列にすべて置き換えるには、メソッドreplace() を使う。 検索は大文字と小文字を区別し、空白文字を含めて行われ、検索文字列に厳密に一致しなければならない。 検索文字列が存在しない場合には、元の文字列をそのまま返す。

次は、文字列 st 内で文字列 "ing" を検索し、見つかったすべての文字列を別の文字列 "ING" に置き換える。 文字列 iing" は見つからないので、ともの文字列が返っている。 先に注意したように、メソッド replace は元の文字列内容は変更されていないことも示してある。

>>> st = "Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do"
>>> st.replace("ing", "ING")
'Alice was beginnING to get very tired of sittING by her sister on the bank, and of havING nothING to do'
>>> st.replace("iing", "ING")
'Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do'
>>> st
'Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do'

文字列では、このようにして既に要されているメソッド replace を使って文字(列)を別の文字(列)に置き換えることきるが、リストにはメソッド replace は用意されていないリスト内包表記などを使って 工夫すれば、リスト要素の入れ替えは可能になる。

2つの検索文字列を「同時に置き換える」

メソッド replace は一回に指定できる検索文字列は「1つだけ」で、見つかったすべてを別の文字列に置き換える。 では、2つの文字列を同時に検索し、それぞれ別の文字列にすべて置き換えるにはどうすればよいだろうか。


たとえば、簡単のために、0と1からなる文字列を考え、0 を 01 に、1 を 0 に同時に置き換える操作 $r$ を考える。

\[ r: \begin{cases} 0\rightarrow 01,\\ 1\rightarrow 0 \end{cases} \]

この操作によって、文字列 0 に操作 $r$ を繰り返し適用することによって次のような文字列を得ることができる。

\begin{align*} r(0) &= 01\\ r^2(0) &=r(0) r(1) = 010,\\ r^3(0) &=r(010) = r(0) r(1) r(0) = 01001,\\ r^4(0) &= r(01001) = 01001010,\\ \dots \end{align*}

文字列 s に対して2つの検索文字列 p1 と p2 を同時に r1 と r2 に置き換えるためには、まず r2 を 与えられた文字列 s には出現し得ない文字列(ここでは "X" とできる)に s.replace(p2, X) で置き換えられた文字列を新しく変数 s1 に割当て、次に文字列 s1 について s1.replace(p1, r1) で置き換えられた文字列を変数 s1 に上書きし、最後に X に置き換えてある文字列 s1 を目的の r1 に s1.replace(X, r1) で置き換えればよい。

今の場合には、次のようなスクリプトになる。

s = '0'
s1 = s.replace('1', 'X')
s1 = s1.replace('0', '01')
s = s1.replace('X', '0')
print(s)
これは、次のスクリプトと同等であることに注意しよう。
s = '0'
s.replace('1', 'X').replace('0', '01').replace('X', '0')
print(s)

この置き換えを繰り返してみよう。 次のスクリプト replace_string.py は 2つ同時の置き換え操作 $r$: 0 → 01, 1 → 0 を5回まで繰り返して置き換え文字列を表示する。


s = "0"
for i in range(1,6):
    s = s.replace('1', 'X').replace('0', '01').replace('X', '0')
	print(i, 'th: ', s)
この実行結果は次のようである。
1 th: 01
2 th: 010
3 th: 01001
4 th: 01001010
5 th: 0100101001001

演習: スクリプト replace_string.py を実行してみなさい。 置き換え操作 $r$ を繰り返すたびに、文字列は$\frac{1+\sqrt{5}}{2}\sim 1.618$倍(黄金比)されて、長くなっていく。 この置き換え操作を Fibonacci置き換えということがある。
演習: a, b, c, d の4文字から文字列を次のように同時に4つ置き換えることを考える。 \[ r: \begin{cases} a\rightarrow ab,\\ b\rightarrow cb,\\ c\rightarrow cd,\\ d\rightarrow ad \end{cases} \] 文字列 "abcd" から初めて、置き換え操作 $r$ を5回繰り返して得られる文字列をプリントするスクリプトを書いて、実行しなさい。

文字列の部分文字列へのリスト分解

文字列は指定した文字列(または文字)によって、メソッドsplit部分文字列のリストに分解される。 特に指定しないときは空白文字" "が指定されたとする。

>>> a = "All you need is love"
>>> a.split()
['All', 'you', 'need', 'is', 'love']
>>> a.split("o")
['All y', 'u need is l', 've']
>>> a.split("ee")
['All you n', 'd is love']
演習: 上の結果を確かめてみなさい。 このようにして、指定した文字列による文字列分解の利用可能性のアイデアを検討してみなさい。