Pythonにおける基本的な文字列操作をまとめました。経験豊富な人には物足りない内容かもしれませんが...
(追記2018.12.23: print
の文法をPython3対応にしました。Python2でコピペしたコードが動かない場合は from __future__ import print_function
をコード辺の先頭に入れてください。)
Pythonの文字列 = immutable(変更不能)
Pythonの文字列はimmutableなので、部分的に書き換えたいというような場合でも、
新しい文字列オブジェクトとして組み立てることになります。
たとえば、文字列置換をおこなうreplace
というメソッドは置換したあとの内容をもつ別の文字列オブジェクトを返します。
連結
連結には+
演算子を使います。
a = 'Python'
b = '2.7'
c = a + b
print(c) # => 'Python2.7'
順番に処理されるのでたくさん連結してもok
a = 'Python'
b = ' is '
c = 'fancy'
print(a + b + c) # => 'Python is fancy'
join
メソッドとリスト/タプルを使って連結するテクニックもありますね。
余談ですがRubyのjoin
はArrayのメソッド(連結文字列を引数にとる)、Pythonのjoin
はstrのメソッド(list/tupleを引数にとる)と、逆になっているのでRubyの経験が豊富な方は注意が必要です。
strings = ['dog', 'cat', 'penguin']
print(','.join(strings)) #=> 'dog,cat,penguin'
繰り返し
同じ内容を繰り返す場合は*
演算子で整数を与えると指定した回数だけリピートした文字列が生成されます。
s = 'dog?'
print(s * 3) #=> 'dog?dog?dog?'
値の埋め込み
文字列に変数の値を展開する際には、3つのやり方があります。もしかしたら私が知らないだけで他にもあるかもしれません。
- sprintfスタイル:
'%s, %s' % ('Hello', 'World')
- 拡張sprintfスタイル:
'%(a)s, %(b)s' % dict(a='Hello', b='World')
- formatメソッド利用:
'{0}, {1}'.format('Hello', 'World')
(注) 二つ目の正確な呼び名はよくわからないのですが、ここでは勝手に拡張sprintfスタイルと呼ぶことにしました。
sprintfスタイル
文字列に %
演算子で値またはlist/tupleを与えると以下のように展開できます。
a = 'Python'
b = 'a programming language'
print('%s is %s' % (a, b)) # => 'Python is a programming language'
c = 'World'
print('Hello, %s!' % c) # => 'Hello, World!'
文字列中の展開記号(%s
など)の個数の数だけ、与える値も必要です。多くても少なくてもいけません。展開記号が1つの場合は%のあとの値はlist/tupleにする必要はありません。(1要素のlist/tupleでも展開されます) 上の例だと最初のprint文のテンプレート文字列は展開記号%s
を2つ含むので、%の後にくる値で与えるタプルの要素の数も2つになっています。テンプレート文字列中で、%
という文字自体を文字として残したい場合は%%
と '%'2文字にするとよい です。
以下のようなフォーマット指定子があります。よくわからないうちはとりあえず%s
にしておくというのも手かもしれません。フォーマット指定子の書き方はprintfのwikipediaページなどに説明を譲りたいと思います。
-
%s
- 文字列として展開 -
%d
- 整数として展開 -
%f
- 小数点数として展開
タプルやリストを '(1, 2, 3)'
のような文字列として展開してほしいときは
tuple_var = (1, 2, 3)
print('tuple_var is: %s' % (tuple_var,))
としないと3個あるのに置換先のプレースホルダが1個しかないよ!みたいに怒られてしまうので注意ですね。
拡張sprintfスタイル
※拡張sprintfスタイルというのは私が勝手につけた名前です(^^;
フォーマット文字列の%
のあとに丸カッコでかこってdictオブジェクトのキーを指定し、フォーマット文字列に対して%
演算子の右辺にdictオブジェクトを指定します。繰り返し同じ値を埋め込む際、すでにdict変数がある場合などに便利でしょう。
v = dict(first='Michael', family='Jackson')
print('He is %(first)s, %(first)s %(family)s.' % v)
formatメソッドの利用
format
メソッドを使うことでformat
メソッド専用のテンプレート言語を使うことができます。
print('{0}, {1}'.format('Hello', 'World')) #=> 'Hello, World'
詳しくは書式指定ミニ言語仕様を見て見てください。
置換(replace)
s = 'Today is Monday.'
ss = s.replace('Monday', 'Sunday') #=> 'Today is Sunday.'
print(ss)
s2 = 'Hello Hello'
ss2 = s2.replace('Hello', 'Bye') #=> 'Bye Bye' 第三引数を指定しなければすべて置換される
print(ss2)
s3 = 'World World'
ss3 = s3.replace('World', 'Hello', 1) #=> 'Hello World' # 第三匹数で置換する個数を指定
print(ss3)
あるパターンに従って文字列を置換する、といった処理はre(正規表現)パッケージのsubメソッドを使います。
import re
s = 'Hello World'
print(re.sub(r"[a-z]", "A", s)) #=> 'HAAAA WAAAA'
N文字目の文字の取得
s = 'abc'
n = 1 # 'a'がほしい
print(s[n-1]) # 0ベースインデックスで文字を取得
s2 = 'xyz'
print(s[-1]) # 'z' 最後の文字
部分文字列の取得(N文字目から、M文字とりだす)
s = "This is a pen."
n = 1
m = 4
print(s[n-1:n-1+m]) # 'This'
print(s[0:4]) # 'This'
print(s[-4:-1]) # 'pen'
検索
find
を使います。後ろ向きに検索したいときはrfind
が使えます。
findは該当文字列が見つかれば0からはじまる文字列位置を, 見つからなければ-1を返します。
s = 'abcabcabc'
index = s.find('b') # indexは1(2文字目)
第二引数で検索を開始する位置を指定できます。
s = 'abcabcabc'
index = s.find('b', 2) # indexは4(5文字目)
以下のようなコードで文字列中からすべてのターゲットを探し出せます。
s = 'abcabcabc'
target = 'b'
index = -1
while True:
index = s.find(target, index + 1)
if index == -1:
break
print('start=%d' % index)
1文字ずつ処理
string型はイテレータでもあるので、以下のようにforで処理できます。文字のlistが欲しければlist(strvalue)
でいいですね。
for c in 'aiueo':
print(c)
print(list('hoge')) # => ['h', 'o', 'g', 'e']
インデックスで文字を参照しながら取り出すというやり方もあるかもしれません。
s = 'aiueo'
for i in range(len(s)):
c = s[i]
print(c)
両端の空白削除
strip
, lstrip
, rstrip
が使えます。
stripは両端からスペース・タブ文字・改行(\rおよび\n)を削除した文字列を、
lstripはstripと同等の処理を左端のみに適用したものを、
rstripはstripと同等の処理を右端のみに適用したものを返します。
s = ' x '
print('A' + s.strip() + 'B') # => 'AxB'
print('A' + s.lstrip() + 'B') # => 'Ax B'
print('A' + s.rstrip() + 'B') # => 'A xB'
改行削除(perlやrubyのchomp相当の処理)
rstrip
でいけそうです。ただ末尾に空白と改行がある2パターンがあり、改行の場合だけ削除したい、というような場合は引数で削除対象の文字を指定する必要があります。
line = 'hoge\n'
msg = line.rstrip() + 'moge'
print(msg) # => 'hogemoge'
with open('./test.txt') as fh:
for line in fh:
no_line_break_line = line.rstrip()
# なにかする
# 空白は削除せずに改行だけ削除する
line_with_space = 'line \n' # 改行の前の空白は削除したくない
print(line_with_space.rstrip('\n')) # => 'line '
全部大文字にする
upper()
メソッドを使います。
print('hello'.upper()) # => 'HELLO'
全部小文字にする
lower()
メソッドを使います。
print('BIG'.lower()) # => 'big'
ある文字列が部分文字列として含まれるかどうか調べる
s = 'abc'
print('b' in s) #=> True
print('x' in s) #=> False
ある文字列が部分文字列として登場する回数を数える
先ほど出てきたfind
メソッドを使って自力でやってもよいですが、count
という便利なメソッドがあります。
s = 'aaabbc'
print(s.count('b')) #=> 2
intを文字列に変換する
v = 1
print(str(v))
print('%d' % v)
floatを文字列に変換する
f = 1.234
print(str(f)) #=> '1.234'
print('%f' % f) #=> '1.234000'
listを文字列に変換する, tupleを文字列に変換する
変換というか、デバッグprintなどで文字列で表現したいことはありますよね。
v = [1,2,3]
print(str(v)) #=> '[1, 2, 3]'
print('%s' % v) #=> '[1, 2, 3]'
tuple1つを%s
で表示しようとすると, Pythonが与えられたtupleをテンプレート用の値のリストと解釈してエラーになってしまいます。
v = (1, 2, 3)
print(str(v)) #=> '(1, 2, 3)' よい例
print('%s' % v) #=> '(1, 2, 3)'を期待するが、TypeErrorになってしまう
print('%s' % (v,)) #=> '(1, 2, 3)' よい例
join
などを使って組み立ててみるのもいいですね。
v = [1,2,3]
print('<' + ('/'.join([ str(item) for item in v ])) + '>') #=> '<1/2/3>'
tuple
オブジェクトも同様です。
dictを文字列に変換する
変換というか、デバッグprintなどで文字列で表現したいことはありますよね。
v = dict(a=1, b=2)
print(str(v)) #=> "{'a': 1, 'b': 2}"
print('%s' % v) #=> "{'a': 1, 'b': 2}"
keys
やリスト内包表記、join
を使ってワンライナーで文字列生成してもいいですね。
v = dict(a=1, b=2)
print('<' + ', '.join([ '%s=%s' % (k, v[k]) for k in v.keys() ]) + '>') #=> '<a=1, b=2>'
バイト列(bytes)をunicode文字列にする
(バイナリモードでオープンされた)ファイルやソケットから読み込んだデータはそのままだとバイト列なので、unicode文字列に解釈してあげないと文字単位の操作がうまくできません。Python2系(2.7など)ではstr(バイト列)とunicode(文字列)は区別されており、Webアプリケーショなど入力にマルチバイト文字が想定されるシーンでは文字列はunicodeオブジェクトとして扱ったほうがよいでしょう。バイト列をエンコーディングを指定してunicode文字列として解釈するためにはdecode()
メソッドを使います。
Python3系では str
型が文字列型(Python2系のunicode型に相当), bytes
型がバイト列型(Python2系のstr型に相当)となっています。
with open('utf8_content_file.txt', 'rb') as fh: # rbなのでバイナリモード
byte_content = fh.read() # ぜんぶ読み込む, この時点ではバイト列
print(len(byte_content)) # バイト数
unicode_string = byte_content.decode('utf-8') # utf-8エンコーディングで、文字の並びとして解釈
print(len(unicode_string)) # 文字数
decode()
メソッドのデフォルトエンコーディングは utf-8
なので解釈対象のバイト列のエンコーディングがUTF-8だとわかっている場合はエンコーディングを省略してもよいでしょう。
bytes_data = b'\xe3\x83\x90\xe3\x82\xa4\xe3\x83\x88\xe5\x88\x97'
print(bytes_data.decode()) # => 'バイト列'
日本語でよく使われるencodingを以下に列挙しておきます。
-
utf_8
UTF-8 (別名:utf-8
U8
utf8
cp65001
) -
shift_jis
シフトJIS (別名:csshiftjis
shiftjis
sjis
s_jis
) -
cp932
シフトJIS(拡張シフトJIS) (別名:932
ms932
mskanji
mks-kanji
) -
euc_jp
EUC-JP (別名:eucjp
ujis
u-jis
) -
iso2022_jp
JIS(ISO-2022-JP) (別名:csiso2022jp
iso2022jp
iso-2022-jp
)
その他にPythonが対応しているエンコーディングは codecs
パッケージのページで見ることができます: https://docs.python.org/ja/3/library/codecs.html
unicode文字列をバイト列(bytes)にする
逆に(バイナリモードでオープンされた)ファイルやソケットに書き込むとき、文字列をバイト列にしないといけないです。そういうときはunicodeオブジェクトのencode()
メソッドをつかいます。
unicode_string = u'マルチバイト文字の文字列'
with open('./utf8_content_file.txt', 'wb') as fh: # 書き込み+バイナリモードでopen
byte_content = unicode_string.encode('utf-8') # utf-8エンコーディングで表現した場合のバイト列を取得
fh.write(byte_content) # バイト列を書き込み
encode()
メソッドもをエンコーディングを渡さないと utf-8
を渡されたのと等価の挙動をします。
str_data = 'バイト列'
print(str_data.encode()) # => b'\xe3\x83\x90\xe3\x82\xa4\xe3\x83\x88\xe5\x88\x97'
テンプレートエンジンを使う
テンプレートエンジンは機能がとても豊富なのでここではいくつかのメジャーなライブラリの紹介にとどめます。
jinja2が一番メジャーでしょうか.