4章 文字列

飛び込む

Python 3では、 すべての文字列はUnicode文字のシーケンスだ。 utf-8でエンコードされたPython文字列や、CP-1252でエンコードされたPython文字列というものは存在しない。

utf-8は文字列をバイト列としてエンコードする方法の1つ。 文字列を特定の文字コードでバイト列に変換したいのであれば、Python 3がそれを助けてくれる。 バイト列を文字列に変換したいのであれば、それもPython 3が助けてくれる。バイトは文字ではない。 バイトはただのバイトで、文字というのは抽象化であり、文字列はその抽象化のシーケンス。

文字列を作るには、クォート文字で囲めばよい。Pythonの文字列はシングルクォート(')でもダブルクォート(")でも定義できる。

組み込みのlen()関数は、文字列の長さ(つまり文字数)を返す。 この関数は、リスト・タプル・辞書・集合の長さを調べるときに使った関数と同じもの。

In [1]:
s = '深入 Python'
len(s)
Out[1]:
9

リストから個々の要素を取り出すのとちょうど同じように、インデックス記法を使って文字列から個々の文字を取り出せる。

In [2]:
s[0]
Out[2]:
'深'

リストと同様に、文字列は+演算子で連結できる。

In [3]:
s + ' 3'
Out[3]:
'深入 Python 3'

文字列をフォーマットする

Python 3は、値を文字列へフォーマットする機能をサポートしている。 基本的な使い方は、1つのプレイスホルダを用いて文字列に値を挿入できる。

In [4]:
username = 'mark'
password = 'PapayaWhip'  
  • これは文字列リテラルのメソッドを呼び出している。文字列はオブジェクトであり、オブジェクトはメソッドを持っているのだ。
  • この式全体が評価された結果は文字列になる。
  • {0}と{1}は「置換フィールド」であり、これらはformat()メソッドに渡された引数によって置換される。
In [5]:
"{0}'s password is {1}".format(username, password)
Out[5]:
"mark's password is PapayaWhip"

合成フィールド名

整数の置換フィールドは、format()メソッドに渡した引数の位置を示すインデックスとして扱われる。

  • {0}は1つ目の引数(この例ではusername)で置換され、
  • {1}は2つ目の引数(この例ではpassword)で置換される。

引数は望むだけいくつでも与えることができ、その引数の数だけインデックスを使うことができる。しかし、置換フィールドはこれよりもっと強力な機能を持っている。

In [6]:
SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
            1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

def approximate_size(size, a_kilobyte_is_1024_bytes=True):
    '''Convert a file size to human-readable form.

    Keyword arguments:
    size -- file size in bytes
    a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
                                if False, use multiples of 1000

    Returns: string

    ''' 
    if size < 0:
        raise ValueError('number must be non-negative')

    multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
    for suffix in SUFFIXES[multiple]:
        size /= multiple
        if size < multiple:
            return '{0:.1f} {1}'.format(size, suffix)
        
    
In [7]:
si_suffixes = SUFFIXES[1000]
si_suffixes
Out[7]:
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']

{0}とすれば、format()関数に与えられた1つ目の引数、つまりsi_suffixesを指すことになる。 si_suffixesはリストなので、

  • {0[0]}は、format()メソッドに1つ目の引数として与えられたリストの最初の要素、つまり'KB'を指している。
  • {0[1]}は同じリストの2つ目の要素、つまり'MB'を指している。

波括弧の外側にあるすべてのもの、つまり1000、イコール記号、空白などはすべてそのままにされる。最終的な結果として'1000KB = 1MB'という文字列が得られる。

In [8]:
'1000{0[0]} = 1{0[1]}'.format(si_suffixes)
Out[8]:
'1000KB = 1MB'

フォーマット指定子はPythonと(ほとんど)同じ構文でデータ構造の要素やプロパティにアクセスできるということだ。これは合成フィールド名と呼ばれる。次のような合成フィールド名が使える:

  • リストを渡して、その要素にインデックスでアクセスする (先の例)
  • 辞書を渡して、その値にキーでアクセスする
  • モジュールを渡して、その変数や関数に名前でアクセスする
  • クラスインスタンスを渡して、そのプロパティやメソッドに名前でアクセスする
  • これらのどんな組み合わせも可能
  • sysモジュールは、現在動作中のPythonインスタンスに関する情報を持っている。2行目でこれをインポートしたので、sysモジュール自体をformat()メソッドの引数として渡すことができる。したがって、{0}という置換フィールドはsysモジュールを指している。

  • sys.modulesは、このPythonインスタンスにおいてインポートされているすべてのモジュールから構成される辞書だ。その辞書のキーはモジュール名の文字列で、値はモジュールオブジェクトそのものだ。つまり置換フィールド{0.modules}はインポートされたモジュールの辞書を指している。

  • sys.modules['humansize']は、今インポートしたhumansizeモジュール。 置換フィールドの{0.modules[humansize]}はhumansizeモジュールを指している。これはPythonのコードとはわずかに違う構文になっていることに注意しよう。 実際のPythonコードでは、辞書sys.modulesのキーは文字列なので、モジュール名の周りをクォートで囲む必要がある(つまり'humansize'とする)。しかし置換フィールドの中では、辞書のキーの周りのクォートは省略する(つまりhumansizeとする)。

PEP 3101: Advanced String Formattingにはこう書かれている。「要素のキーをパースする方法は非常に単純だ。数字から始まる場合は数値として扱われ、それ以外の場合は文字列として扱われる」

  • sys.modules['humansize'].SUFFIXES は、humansizeモジュールの先頭で定義されている辞書。 置換フィールド{0.modules[humansize].SUFFIXES}は、この辞書を指している。

  • sys.modules['humansize'].SUFFIXES[1000]は、si接尾語のリスト['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']になる。

  • sys.modules['humansize'].SUFFIXES[1000][0]は、si接尾語のリストの最初の要素'KB'.

import humansize
import sys

'1MB = 1000{0.modules[humansize].SUFFIXES[1000][0]}'.format(sys)

'1MB = 1000KB'

フォーマット指定子

'{0:.1f} {1}'.format(size, suffix)

{1}は、format()メソッドの2番目の引数suffixで置換される。

この後半部分(コロン以降)はフォーマット指定子というもので、置換する変数をどのようにフォーマットするのかをさらに詳細に指定するためのもの。

フォーマット指定子は、C言語の printf() 関数のように、置換するテキストを様々な方法で加工できる。例えば、ゼロや空白でパディングしたり、文字列を揃えたり、小数の精度を整えたりできる。数値を16進数に変換することさえ可能.

  • 置換フィールドの中では、コロン(:)はフォーマット指定子の開始を表す。

  • フォーマット指定子“.1”は「最も近い小数第1位の数へ丸める」ことを表す(つまり小数点の後ろには数字を1つだけ表示する)。

  • フォーマット指定子 "f" は「固定小数表記」にすることを表す(指数表記やその他の小数記法ではなく)。

たとえば、698.24という値を持つsizeと、'GB'という値を持つsuffixを与えれば、698.24は小数第一位へ丸められ、そこに接尾語が付け足されることによって、フォーマット結果の文字列は'698.2 GB'になるだろう。

In [9]:
'{0:.1f} {1}'.format(698.24, 'GB')
Out[9]:
'698.2 GB'

その他の一般的な文字列メソッド

フォーマットだけではなく、文字列には他にもいくつかの便利なテクニックがある。

Pythonの対話シェルで複数行文字列を入力できる。 三重クォート'''で複数行文字列を開始して、 ENTERキーを押すと、 対話シェルは次行の入力をうながしてくる。 三重クォートで文字列を終了させ、ENTERキーを押すとコマンドが実行される(この例では文字列がsという変数に代入される)。

In [10]:
 s = '''Finished files are the re-
 sult of years of scientif-
 ic study combined with the
 experience of years.'''

splitlines()メソッドは、複数行の文字列を受け取って、その文字列の各行からなる文字列のリストを返す。各行の行末にある改行文字は含まれないことに注意しよう。

In [11]:
s.splitlines()
Out[11]:
['Finished files are the re-',
 'sult of years of scientif-',
 'ic study combined with the',
 'experience of years.']

lower()メソッドは文字列の全体を小文字に変換する (同様に、upper()メソッドは文字列を大文字に変換する)。

In [12]:
print(s.lower())
finished files are the re-
sult of years of scientif-
ic study combined with the
experience of years.

count()は、部分文字列が文字列中に出現する回数を数える。

In [13]:
s.lower().count('f')
Out[13]:
6

例えば、ここに key1=value1&key2=value2 という形式の、キーと値のペアからなるリストがあるとする。これを分解して{key1: value1, key2: value2} のような辞書を作りたいとする。

In [14]:
query = 'user=pilgrim&database=master&password=PapayaWhip'

文字列のsplit()メソッドには、必ず引数として区切り文字を渡さなくてはならない。 その区切り文字に基づいて、このメソッドは文字列をリストへ分割する。 ここでの区切り文字はアンパサンド(&)だが、どんな文字列でも区切り文字として使える。

In [15]:
a_list = query.split('&')
a_list
Out[15]:
['user=pilgrim', 'database=master', 'password=PapayaWhip']

文字列のリストを手に入れた。各々の文字列には、キーがあり、その後ろにはイコール記号が続き、さらにその後ろに値が続く。 リスト全体をイテレートして、各々の文字列を1つ目のイコール記号に基づいて2つに分割していくには、リスト内包表記が使える。

split()メソッドの2番目の引数(オプション)は、 分割したい回数。 1は「1回だけ分割する」ことを意味するので、split()メソッドは2つの要素をもつリストを返す。 (理論上は、値がイコール記号を含んでいる可能性がある。もし、単純に'key=value=foo'.split('=')としていたら、 結果として3つの要素を持つリスト['key'、'value'、'foo']が得られてしまうかもしれない)。

In [16]:
a_list_of_lists = [v.split('=', 1) for v in a_list if '=' in v]
a_list_of_lists
Out[16]:
[['user', 'pilgrim'], ['database', 'master'], ['password', 'PapayaWhip']]

最後に、リストのリストをdict()関数に渡すだけで、Pythonはそれを辞書に変換してくれる。

In [17]:
a_dict = dict(a_list_of_lists)
a_dict
Out[17]:
{'database': 'master', 'password': 'PapayaWhip', 'user': 'pilgrim'}

この例は、urlのクエリパラメータをパースしているように見えるが、 現実世界のurlはこれよりも複雑。 urlのクエリパラメータを扱うときは、urllib.parse.parse_qs()関数を使うと楽。この関数は特殊なケースもうまく扱ってくれる。

文字列をスライスする

文字列を定義したら、その文字列の一部を新しい文字列として取り出すことができる。これは文字列の「スライス」と呼ばれる。文字列のスライスはリストのスライスとまったく同様に動作する。

In [18]:
a_string = 'My alphabet starts where your alphabet ends.'

文字列の一部を取り出すことができる。この操作は「スライス」と呼ばれ、2つのインデックスを指定することによって行う。戻り値は新しい文字列であり、1つ目のスライスインデックス以降の文字を、順番どおりに含んでいる。

In [19]:
a_string[3:11]
Out[19]:
'alphabet'

リストのスライスと同様に、文字列のスライスには負のインデックスが使える。

In [20]:
a_string[3:-3]
Out[20]:
'alphabet starts where your alphabet en'
In [21]:
a_string[0:2]
Out[21]:
'My'

左側のスライスインデックスが0のときはこれを省略できる。 つまり、a_string[:18]はa_string[0:18]と同じ。

In [22]:
a_string[:18]
Out[22]:
'My alphabet starts'

同様に、右側のスライスインデックスが文字列の長さと同じときはこれを省略できる。 つまりa_string[18:]は、文字列の長さが44文字なのでa_string[18:44] と同じになる。ここには気持ちの良い対称性が存在する。この44文字の文字列では、a_string[:18]は最初の18文字を返し、a_string[18:]最初の18文字以外を返す。実際に、文字列の長さにかかわらず、a_string[:n]は常に最初のn文字を返すし、a_string[n:]はその残りを返す。

In [23]:
a_string[18:]
Out[23]:
' where your alphabet ends.'

文字列 vs バイト列

バイトは単なるバイトであり、文字は抽象化だ。 Unicode文字のイミュータブルなシーケンスは「文字列」と呼ばれ、 0から255までの数のイミュータブルなシーケンスは「バイト列」と呼ばれる。

bytesオブジェクトを定義するには、b''という「バイトリテラル」構文を使えばよい。

バイトリテラルの各バイトには、

  • ascii文字
  • 16進数にエンコードされた\x00から\xffまでの数値 (0–255)

が使える。

In [24]:
by = b'abcd\x65'
by
Out[24]:
b'abcde'

bytesオブジェクトの型はbytes。

In [25]:
type(by)
Out[25]:
bytes

リストや文字列と同様に、+演算子を使ってbytesオブジェクトを連結できる。 結果は新しいbytesオブジェクト。

In [26]:
by += b'\xff'
by
Out[26]:
b'abcde\xff'
In [27]:
len(by)
Out[27]:
6

リストや文字列と同様に、インデックス記法によってbytesオブジェクトの個々のバイトを取り出せる。 文字列の要素は文字列だが、 bytesオブジェクトの要素は整数。厳密に言えば、0から255までの整数だ。

In [28]:
by[0]
Out[28]:
97

bytesオブジェクトはイミュータブル。 個々のバイトへ代入することはできない。もし個々のバイトを変更する必要があるときは、文字列スライスと結合演算子(これは文字列と同様に機能する)を使うこともできるし、bytesオブジェクトをbytearrayオブジェクトに変換することもできる。

In [29]:
by[0] = 102
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-29-bb4ece50b6ff> in <module>()
----> 1 by[0] = 102

TypeError: 'bytes' object does not support item assignment
In [30]:
by = b'abcd\x65'

bytesオブジェクトを変更可能なbytearrayオブジェクトに変換するには、組み込みのbytearray()関数を使う。

In [31]:
barr = bytearray(by)
barr
Out[31]:
bytearray(b'abcde')
In [32]:
len(barr)
Out[32]:
5

byteとbytearrayの1つの違いは、 bytearrayオブジェクトではインデックス記法を使って個々のバイトへ代入できる。代入する値は0から255までの整数でなければならない。

In [33]:
barr[0] = 102
barr
Out[33]:
bytearray(b'fbcde')

バイト列と文字列は決して混ぜることができない。

In [34]:
by = b'd'
s = 'abcde'
In [35]:
by + s
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-35-2752bca4b043> in <module>()
----> 1 by + s

TypeError: can't concat bytes to str

バイト列が文字列中に出現する回数を数えることはできない。文字列の中にバイト列というものは存在しない。

文字列は文字のシーケンス。 もしかすると「バイト列を特定の文字コードを使って文字列にデコードし、その文字列の出現回数を数えよ」ということをしたいのであれば、 それを明示的に行う必要がある。

Python 3が暗黙にバイト列を文字列に変換したり、文字列をバイト列に変換することはない

In [36]:
s.count(by)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-36-14a8d8da7d7a> in <module>()
----> 1 s.count(by)

TypeError: Can't convert 'bytes' object to str implicitly

この行のコードは、「このバイト列を指定した文字コードでデコードして得られる文字列の出現回数を数えよ」

In [37]:
s.count(by.decode('ascii'))
Out[37]:
1

文字列とバイト列のあいだの関係:

  • bytesオブジェクトはdecode()メソッドを持っていて、これは文字コードを受け取って文字列を返す。

  • 文字列はencode()メソッドを持っていて、これは文字コードを受け取ってbytesオブジェクトを返す。

前の例は、asciiコードのバイト列を文字列に変換するというもので、デコード処理は比較的単純だ。しかし同様の処理は、文字列に含まれている文字をサポートしているものなら、どんな文字コードでも(非Unicodeエンコーディングでも)動作する。

文字列。9つの文字を含んでいる。

In [38]:
a_string = '深入 Python'
len(a_string)
Out[38]:
9

bytesオブジェクトで、13バイトある。これはa_stringをutf-8でエンコードしたときに得られるバイト列。

In [39]:
by = a_string.encode('utf-8')
by
Out[39]:
b'\xe6\xb7\xb1\xe5\x85\xa5 Python'
In [40]:
len(by)
Out[40]:
13

これはa_stringをGB18030でエンコードしたときに得られるバイト列。

In [41]:
by = a_string.encode('gb18030')
by
Out[41]:
b'\xc9\xee\xc8\xeb Python'
In [42]:
len(by)
Out[42]:
11

bytesオブジェクトで、11バイトある。これはa_stringをBig5でエンコードしたときに得られるもので、先のものとは全く異なったバイト列になっている。

In [43]:
by = a_string.encode('big5')
by
Out[43]:
b'\xb2`\xa4J Python'
In [44]:
len(by)
Out[44]:
11

これは文字列で、9つの文字がある。 これはbyをBig5エンコーディングアルゴリズムでデコードしたときに得られる文字列。これは元の文字列に等しい。

In [45]:
roundtrip = by.decode('big5')
roundtrip
Out[45]:
'深入 Python'
In [46]:
a_string == roundtrip
Out[46]:
True

Pythonのソースコードの文字コード

Python3は、ソースコード(つまり.pyファイル)がutf-8でエンコードされていると想定する。

もしPythonのコードで異なる文字コードを使いたい場合は、文字コード宣言を各ファイルの先頭に書くことができる。

次の宣言は、.pyファイルの文字コードをwindows-1252に設定している:

# -*- coding: windows-1252 -*-

厳密に言うと、1行目がunix系環境で使われるシェバン (#!) である場合は、文字コードの宣言は2行目でもよい。

#!/usr/bin/python3
# -*- coding: windows-1252 -*-

参考リンク