Python文字列操作マスター

Pythonにおける基本的な文字列操作をまとめました。経験豊富な人には物足りない内容かもしれませんが...

(追記2018.12.23: `print` の文法をPython3対応にしました。Python2でコピペしたコードが動かない場合は `from __future__ import print_function` をコード辺の先頭に入れてください。)

## Pythonの文字列 = immutable(変更不能)

Pythonの文字列はimmutableなので、部分的に書き換えたいというような場合でも、
新しい文字列オブジェクトとして組み立てることになります。
たとえば、文字列置換をおこなう`replace`というメソッドは置換したあとの内容をもつ別の文字列オブジェクトを返します。

## 連結

連結には`+`演算子を使います。

```python
a = 'Python'
b = '2.7'
c = a + b
print(c)  # => 'Python2.7'
```

順番に処理されるのでたくさん連結してもok

```python
a = 'Python'
b = ' is '
c = 'fancy'
print(a + b + c)  # => 'Python is fancy'
```

`join`メソッドとリスト/タプルを使って連結するテクニックもありますね。
余談ですがRubyの`join`はArrayのメソッド(連結文字列を引数にとる)、Pythonの`join`はstrのメソッド(list/tupleを引数にとる)と、逆になっているのでRubyの経験が豊富な方は注意が必要です。

```python
strings = ['dog', 'cat', 'penguin']
print(','.join(strings))  #=> 'dog,cat,penguin'
```


## 繰り返し

同じ内容を繰り返す場合は`*`演算子で整数を与えると指定した回数だけリピートした文字列が生成されます。

```python
s = 'dog?'
print(s * 3)  #=> 'dog?dog?dog?'
```

## 値の埋め込み

文字列に変数の値を展開する際には、3つのやり方があります。もしかしたら私が知らないだけで他にもあるかもしれません。

1. sprintfスタイル: `'%s, %s' % ('Hello', 'World')`
2. 拡張sprintfスタイル: `'%(a)s, %(b)s' % dict(a='Hello', b='World')`
3. formatメソッド利用: `'{0}, {1}'.format('Hello', 'World')`

(注) 二つ目の正確な呼び名はよくわからないのですが、ここでは勝手に拡張sprintfスタイルと呼ぶことにしました。

### sprintfスタイル

文字列に `%` 演算子で値またはlist/tupleを与えると以下のように展開できます。

```python
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ページ](http://ja.wikipedia.org/wiki/Printf)などに説明を譲りたいと思います。

- `%s` - 文字列として展開
- `%d` - 整数として展開
- `%f` - 小数点数として展開

タプルやリストを `'(1, 2, 3)'` のような文字列として展開してほしいときは

```python
tuple_var = (1, 2, 3)
print('tuple_var is: %s' % (tuple_var,))
```

としないと3個あるのに置換先のプレースホルダが1個しかないよ!みたいに怒られてしまうので注意ですね。

### 拡張sprintfスタイル

※拡張sprintfスタイルというのは私が勝手につけた名前です(^^;

フォーマット文字列の`%`のあとに丸カッコでかこってdictオブジェクトのキーを指定し、フォーマット文字列に対して`%`演算子の右辺にdictオブジェクトを指定します。繰り返し同じ値を埋め込む際、すでにdict変数がある場合などに便利でしょう。

```python
v = dict(first='Michael', family='Jackson')
print('He is %(first)s, %(first)s %(family)s.' % v)
```

### formatメソッドの利用

`format`メソッドを使うことで`format`メソッド専用のテンプレート言語を使うことができます。

```python
print('{0}, {1}'.format('Hello', 'World'))  #=> 'Hello, World'
```

詳しくは[書式指定ミニ言語仕様](http://docs.python.jp/2/library/string.html#formatspec)を見て見てください。


## 置換(replace)

```python
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(正規表現)パッケージ](http://docs.python.jp/2.7/library/re.html)のsubメソッドを使います。

```python
import re
s = 'Hello World'
print(re.sub(r"[a-z]", "A", s))  #=> 'HAAAA WAAAA'
```

## N文字目の文字の取得

```python
s = 'abc'
n = 1  # 'a'がほしい
print(s[n-1])  # 0ベースインデックスで文字を取得

s2 = 'xyz'
print(s[-1])  # 'z' 最後の文字
```

## 部分文字列の取得(N文字目から、M文字とりだす)

```python
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を返します。

```python
s = 'abcabcabc'
index = s.find('b')  # indexは1(2文字目)
```

第二引数で検索を開始する位置を指定できます。

```python
s = 'abcabcabc'
index = s.find('b', 2)  # indexは4(5文字目)
```

以下のようなコードで文字列中からすべてのターゲットを探し出せます。

```python
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)`でいいですね。

```python
for c in 'aiueo':
	print(c)

print(list('hoge'))  # => ['h', 'o', 'g', 'e']
```

インデックスで文字を参照しながら取り出すというやり方もあるかもしれません。

```python
s = 'aiueo'
for i in range(len(s)):
	c = s[i]
	print(c)
```

## 両端の空白削除

`strip`, `lstrip`, `rstrip`が使えます。
stripは両端からスペース・タブ文字・改行(\rおよび\n)を削除した文字列を、
lstripはstripと同等の処理を左端のみに適用したものを、
rstripはstripと同等の処理を右端のみに適用したものを返します。

```python
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パターンがあり、改行の場合だけ削除したい、というような場合は引数で削除対象の文字を指定する必要があります。

```python
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()`メソッドを使います。 

```python
print('hello'.upper())  # => 'HELLO'
```

## 全部小文字にする

`lower()`メソッドを使います。

```python
print('BIG'.lower())  # => 'big'
```

## ある文字列が部分文字列として含まれるかどうか調べる

```python
s = 'abc'
print('b' in s)  #=> True
print('x' in s)  #=> False
```

## ある文字列が部分文字列として登場する回数を数える

先ほど出てきた`find`メソッドを使って自力でやってもよいですが、`count`という便利なメソッドがあります。

```python
s = 'aaabbc'
print(s.count('b'))  #=> 2
```

## intを文字列に変換する

```python
v = 1
print(str(v))
print('%d' % v)
```

## floatを文字列に変換する

```python
f = 1.234
print(str(f))  #=> '1.234'
print('%f' % f)  #=> '1.234000'
```

## listを文字列に変換する, tupleを文字列に変換する

変換というか、デバッグprintなどで文字列で表現したいことはありますよね。

```python
v = [1,2,3]
print(str(v))  #=> '[1, 2, 3]'
print('%s' % v)  #=> '[1, 2, 3]'
```

tuple1つを`%s`で表示しようとすると, Pythonが与えられたtupleをテンプレート用の値のリストと解釈してエラーになってしまいます。

```python
v = (1, 2, 3)
print(str(v)) #=> '(1, 2, 3)' よい例
print('%s' % v) #=> '(1, 2, 3)'を期待するが、TypeErrorになってしまう
print('%s' % (v,)) #=> '(1, 2, 3)' よい例
```

`join`などを使って組み立ててみるのもいいですね。

```python
v = [1,2,3]
print('<' + ('/'.join([ str(item) for item in v ])) + '>')  #=> '<1/2/3>'
```

`tuple`オブジェクトも同様です。

## dictを文字列に変換する

変換というか、デバッグprintなどで文字列で表現したいことはありますよね。

```python
v = dict(a=1, b=2)
print(str(v))  #=> "{'a': 1, 'b': 2}"
print('%s' % v)  #=> "{'a': 1, 'b': 2}" 
```

`keys`やリスト内包表記、`join`を使ってワンライナーで文字列生成してもいいですね。

```python
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型に相当)となっています。

```python
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()`メソッドをつかいます。

```python
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` を渡されたのと等価の挙動をします。

```python
str_data = 'バイト列'
print(str_data.encode()) # => b'\xe3\x83\x90\xe3\x82\xa4\xe3\x83\x88\xe5\x88\x97'
```

## テンプレートエンジンを使う

テンプレートエンジンは機能がとても豊富なのでここではいくつかのメジャーなライブラリの紹介にとどめます。

- [jinja2](http://jinja.pocoo.org/)
- [mako](http://www.makotemplates.org/)

jinja2が一番メジャーでしょうか.

## 参考リンク

- [7.1. string — 一般的な文字列操作(Python2.7)](http://docs.python.jp/2.7/library/string.html)
- [6.1. string — 一般的な文字列操作(Python3.5.1)](http://docs.python.jp/3.5/library/string.html)