現在地: ホーム Dive Into Python 3

難易度: ♦♦♦♢♢

文字列

私がこれを教えるのも、お前が私の友達だからだ。 私のアルファベットは、お前のアルファベットが終わるところから始まるのだよ!
— Dr. Seuss, On Beyond Zebra!

 

飛び込む前に知っておくべき退屈なこと

ほとんどの人は気にもとめないのだが、テキストというのは信じられないほど複雑だ。まずはアルファベットから始めていこう。ブーゲンビル島の人々は、世界で最も字数の少ないアルファベットを使っている。彼らのロトカスアルファベットは、たったの12文字 — A, E, G, I, K, O, P, R, S, T, U, V で構成されている。これとは対照的に、中国語・日本語・韓国語のように何千もの文字を持つ言語もある。もちろん英語には、26文字(大文字と小文字を分けて数えれば52文字)と、わずかな !@#$%& 句読点がある。

皆さんが「テキスト」について話すとき、おそらく「コンピュータスクリーン上の文字とシンボル」のことを思い浮かべるだろう。ところが、コンピュータは文字やシンボルを扱わない。コンピュータはビットやバイトを扱うのだ。今までにコンピュータスクリーンの上で見たすべてのテキストは、実際には何らかの「文字コード」で記録されている。非常に大ざっぱに言えば、文字コードというのは、私たちが画面上で見るものと、コンピュータが実際にメモリやディスクに記録するものとを対応させるものといえる。多種多様な文字コードが存在していて、ロシア語・中国語・英語といった特定の言語に最適化されているものもあれば、複数の言語で使えるものもある。

現実の事情はもっと複雑だ。多くの文字は複数の文字コードに共通して存在するが、個々の文字コードは、それらの文字を実際にメモリやディスクに格納する際に異なるバイト列を用いているかもしれない。つまり、文字コードというのは、暗号解読のための鍵のようなものだと捉えられる。だから、誰かからバイト列(それがファイルであれ、Webページであれ、その他何であれ)を受け取って、その内容が「テキスト」だと告げられた場合、そのバイトを文字へデコードするには、彼らが何の文字コードを使用したのかを知る必要がある。もし間違った鍵を渡されたり、あるいは全く何も渡してくれなかったとしたら、自分自身で文字コードを解析するというイヤな作業をするしかなくなってしまう。たぶん、正しい文字コードを引き当てられず、メチャクチャな結果が返ってくるのがオチだろう。

おそらく、アポストロフィ記号が奇妙なクエスチョンマーク状の文字に化けてしまっているWebページを見た経験があると思う。大抵の場合、これはページの作者が文字コードを正しく宣言しておらず、ブラウザが文字コードを推測するしかなくなってしまい、本来表示されるべき文字とそうでない文字が混じり合った結果になってしまった、ということを意味している。英語では多少わずらわしいだけだが、他の言語においては、まったく読むことのできないページが表示される可能性がある。

世界の主要な言語には、その言語のための文字コードが存在している。言語はそれぞれが異なるものであるし、歴史的にはメモリもディスクも高価なものだったので、個々の文字コードは特定の1つの言語のために最適化されているのだ。要するに、私が言いたいのは、各々のエンコーディングは同じ数字 (0–255) を使ってその言語の文字を表現しているということだ。例えば、おそらく皆さんが慣れ親しんでいるであろうASCIIコードは英語の文字を0から127までの範囲の数字で格納している(65は大文字の"A"、97は小文字の"a"など)。英語は非常に単純なアルファベットを持っているので、128個以下の数字で完全に表現できる。2進数で数をかぞえられる皆さんのために言っておくと、128というのは1バイトを構成する8ビットのうちの7ビットだ。

フランス語・スペイン語・ドイツ語のような西欧の言語は、英語よりもたくさんの文字を持っている。もっと厳密に言うと、これらの言語には様々な発音区別符号と組み合わさったアルファベット(たとえばスペイン語のñ)があるのだ。これらの言語のための最も一般的な文字コードはCP-1252で、これはMicrosoft Windowsで広く使われているので "windows-1252" とも呼ばれている。CP-1252は、0–127の範囲ではASCIIと同じ文字になっているが、n-with-a-tilde-over-it (241) や u-with-two-dots-over-it (252) といった文字のために128–255の範囲まで拡張されている。とはいえ、これは依然として1バイトの文字コードだ。つまり、取りうる最大の数値が255なので、やはり1バイトの範囲に収まるのだ。

中国語・日本語・韓国語のような言語は大量の文字を持っているので、マルチバイトの文字集合が必要になる。要するに、各々の「文字」が0から65535までの2バイトの数で表現されるのだ。しかし、マルチバイトの文字コードは、依然としてシングルバイトの文字コードと同じ問題を抱えている。すなわち、同じ数値であっても文字コードごとに表現している文字が異なるという問題だ。マルチバイトの文字コードは、表現する文字が多いために数値の範囲を広げたというだけにすぎない。

ネットワーク化されていない世界、つまり「テキスト」は自分自身で入力したものばかりで、たまにそれを印刷するような状況では、これでも概ねうまくいっていた。「プレーンテキスト」なんてものはそれほど多くなかった。ソースコードはASCIIで書かれていたし、他のみんなはワードプロセッサを使っていた。ワードプロセッサは(テキスト形式ではない)独自の形式を定義していて、文字装飾などの情報と一緒に文字コードの種類も管理している。人々は作者と同じワードプロセッサのプログラムを使って文章を読んでいたので、多かれ少なかれ、すべてがうまくいっていた。

今度は、Webや電子メールのような世界規模のネットワークの出現について考えてみよう。大量の「プレーンテキスト」が地球上を飛び回っており、1台目のコンピュータ上で書かれたテキストが、2台目のコンピュータを経由して転送され、3台目のコンピュータで受信されて表示される。コンピュータは数字しか理解しないが、この数字は様々なものを意味しうる。まいった、どうしたら良いのだろうか? そう、システムは「プレーンテキスト」と一緒に、文字コードの情報を送り届けるように設計しなければならないのだ。思い出してほしい。文字コードの情報は、機械が読める数字を人間が読める文字へマッピングする暗号解読の鍵だ。暗号解読の鍵がないということは、不明瞭なテキストか、ちんぷんかんぷんなテキストか、もっとメチャクチャな何かが得られることを意味する。

今度は、複数のテキストを同じ場所に格納しようとすることを考えてみよう。これは例えば、今までに受信したすべての電子メールを、データベースの同じテーブルに格納するような場合だ。ここでも、メールを正しく表示するためには、それぞれの文書と一緒にその文字コードを格納しておかなければならない。これは大変そうではないだろうか? 電子メールのデータベースを検索しようとするときは、複数の文字コードを急いで変換しなければならない。これは面白そうに思えるだろうか?

今度は、複数の言語で書かれた文章について考えよう。この文章では、様々な言語の文字が1つの文章の中でそれぞれ隣り合っている(ヒント: このようなことをするプログラムは、典型的にはエスケープコードを使って「モード」を切り替える。パッ! 今はロシア語のkoi8-rモードなので、241はЯです。パッ! 今はMacのギリシャ語モードなので、241はώです)。そしてもちろん、このようなドキュメントも検索したいと思うだろう。

もう泣くしかない。文字列について知っていると思っていたことはすべて間違いで、「プレーンテキスト」などというものは存在しないのだから。

Unicode

Unicodeの話に入ろう。

Unicodeは、すべての言語のすべての文字を表現するために設計されたシステムだ。Unicodeは、すべての文字や記号を4バイトの数値で表現していて、各々の数値は少なくとも世界中のどれか1つの言語で使われているただ1つの文字を表している(4バイトの数値がすべて使われているわけではないが、65535より多くの数値が使われているので、2バイトでは十分ではないのだ)。複数の言語で使われる文字は語源的な理由がない限り基本的に同じ数値だ。とにかく、1つの文字は1つの数値に対応し、1つの数値は1つの文字に対応する。すべての数値は常にただ1つのものを意味しており、「モード」などというものは存在しない。たとえあなたの言語に'A'という文字が無いとしても、U+0041は常に'A'を表すのだ。

一見したところ、これは素晴らしいアイデアのように思える。全てを統括する1つの文字コード。1つの文書にいくつもの言語。言語エンコーディングを途中で切り替えるための「モードスイッチ」なんてもう必要ない。しかし、すぐに明らかな疑問が浮かび上がってくるだろう。4バイトだって? すべての文字で?! これはひどく無駄が多いように思えるし、英語やスペイン語のように、1バイト(256個の数値)以下で全ての文字を表現できるような言語に対しては特にそう感じられる。実をいうと(中国語のような)表意文字の言語でさえも2バイトしか必要としないので、これらの言語であっても無駄が多いのだ。

1文字につき4バイトを使うUnicodeエンコーディングがある。4バイト = 32ビットなので、これはUTF-32と呼ばれる。UTF-32は率直な文字コードであり、Unicode文字(4バイトの数値)を受け取って、それと同じ数値でその文字を表現する。これはいくつかの利点を持つが、一番の利点は、N番目の文字が4 × Nバイト目から始まるので、N番目の文字を定数時間で見つけられることだ。欠点もいくつかあるが、最も明らかなのは、1文字につき4バイトものバイト数を必要とすることだ。

たくさんのUnicode文字があるにも関わらず、結局のところ、ほとんどの人々は65535を超える文字を決して使わないということが分かった。そこで、もう一つのUnicodeエンコーディングとして、UTF-16(16ビット = 2バイトだから)がある。UTF-16は、0から65535までの文字を2バイトとしてエンコードするが、もしも滅多に使われない65535を超える「幻想世界」のUnicode文字を本当に表現しなければいけない場合は、汚い手法を使って解決される。UTF-16の最も明らかな利点は、各文字が(例外を除き)4バイトではなく2バイトで表現されるので、容量をUTF-32の半分しか使わないということだ。そして、文字列に幻想世界の文字が含まれていない限り(ほとんどの場合はそうだ)、依然としてN番目の文字を定数時間で見つけることができる。

しかし、UTF-32とUTF-16の両者には、気づきにくい問題点も存在する。問題は個々のバイトを格納する方法がコンピューターシステムによってまちまちだということにある。例えば、U+4E2Dという文字は、UTF-16では4E 2D2D 4Eのどちらの順番でも格納される可能性があり、これはシステムがリトルエンディアンとビッグエンディアンのどちらであるかによって決まる(UTF-32では、さらに考えうるバイトの順序が増える)。同じコンピュータ上では異なるアプリケーションであっても基本的に同じバイトオーダーが使われるので、文章がコンピュータの外に出ない限りは安全だ。しかし、World Wide Webか何かを使って文章を異なるシステム間で転送するとなれば、そのバイトがどのような順序で格納されているのかを示すための手段が必要になる。さもなければ、受け取ったシステムは、2バイトのシーケンス4E 2Dが、U+4E2DU+2D4Eのどちらを表しているのか判断できない。

この問題を解決するために、マルチバイトのUnicodeエンコーディングは「バイトオーダーマーク」を定義している。これは特別な非印字文字であり、文章のバイトオーダーを知らせるために、文書の先頭に付けることができる。UTF-16でのバイトオーダーマークはU+FEFFだ。FF FEで始まる文書を受け取った場合には、バイトが一方の順序で並んでいることが分かるし、FE FFで始まる文章を受け取った場合は、バイトがそれとは逆の順序で並んでいるということが分かる。

しかし、以上を踏まえてもUTF-16が完全に理想的だとは言いがたい。たくさんのASCII文字を扱っている場合は特にそうだ。考えてみてほしい、中国語のWebページですら多くのASCII文字を使っているのだ — 実際に表示される中国語を囲んでいる(マークアップの)要素や属性はみんなASCIIだ。N番目の文字を定数時間で見つけられるのは良いことだが、依然として幻想世界の文字に関するしつこい問題が存在するため、すべての文字が確実に2バイトだという保証はない。だから、別途にインデックスでも保持しておかない限り、N番目の文字を本当に定数時間で見つけることは不可能だ。しかもおまけに、この世界には本当に大量のASCII文章が存在するのだ……

この問題を深く考えた人たちがいて、彼らは1つの解決策を思いついた:

UTF-8

UTF-8は、Unicodeのための可変長のエンコード体系だ。すなわち、使うバイト数が文字によって異なるのだ。ASCII文字(A-Zなど)の範囲では、UTF-8は1文字につき1バイトしか使わない。実をいうと、UTF-8の最初の128文字(0から127まで)は、ASCIIと全く同じバイト表現を使うので、両者は区別できない。ñやöのような「拡張ラテン」文字は2バイトを使うことになる(UTF-16で表したときとは違い、この2バイトはUnicodeのコードポイントそのものではない。ここには本格的なビット操作が加えられている)。「中」のような中国語の文字は3バイトを使うことになる。そして、めったに使われない「幻想世界」の文字は4バイトを使う。

欠点: それぞれの文字が異なるバイト数を使うので、N番目の文字見つけ出す処理はO(N)の演算になる。つまり、文字列が長くなるほど、特定の文字を見つけるのに時間が掛かってしまう。また、文字列からバイト列へのエンコードや、バイト列から文字列へのデコードを行う際にビット操作が必要になる。

利点: 一般のASCII文字を扱う場合には非常に効率的な文字コードだ。拡張ラテン文字を扱う場合にもUTF-16より悪くなることはない。中国語をUTF-32で扱うよりは良い。そして、(詳細を示すつもりはないので、私の言うことを信じてもらうしかないのだが)このビット操作のもつ性質により、バイトオーダーの問題も発生しない。UTF-8でエンコードされた文章は、どのコンピュータ上でも全く同じバイトのストリームになる。

飛び込む

Python 3では、すべての文字列はUnicode文字のシーケンスだ。UTF-8でエンコードされたPython文字列や、CP-1252でエンコードされたPython文字列というものは存在しない。「この文字列はUTF-8なの?」という質問は当を得ないものだ。UTF-8は文字列をバイト列としてエンコードする方法の1つなのだ。文字列を特定の文字コードでバイト列に変換したいのであれば、Python 3がそれを助けてくれる。バイト列を文字列に変換したいのであれば、それもPython 3が助けてくれる。バイトは文字ではない。バイトはただのバイトで、文字というのは抽象化であり、文字列はその抽象化のシーケンスなのだ。

>>> s = '深入 Python'    
>>> len(s)               
9
>>> s[0]                 
'深'
>>> s + ' 3'             
'深入 Python 3'
  1. 文字列を作るには、クォート文字で囲めばよい。Pythonの文字列はシングルクォート(')でもダブルクォート(")でも定義できる。
  2. 組み込みのlen()関数は、文字列の長さ(つまり文字数)を返す。この関数は、リスト・タプル・辞書・集合の長さを調べるときに使った関数と同じものだ。文字列というのは文字のタプルのようなものなのだ。
  3. リストから個々の要素を取り出すのとちょうど同じように、インデックス記法を使って文字列から個々の文字を取り出せる。
  4. リストと同様に、文字列は+演算子で連結できる。

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

humansize.pyを別の視点から見てみよう

[humansize.pyをダウンロードする]

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)                       

    raise ValueError('number too large')
  1. 'KB', 'MB', 'GB'…… これらは文字列だ。
  2. 関数のdocstringも文字列だ。このdocstringは複数行にわたるので、文字列の開始と終了に三重クォートを使っている。
  3. この三重クォートでdocstringが閉じられる。
  4. ここにも文字列があり、人間が読むことのできるエラーメッセージとして例外に渡されている。
  5. ここにも文字列が…… むむ、こいつは一体なんだ?

Python 3は、値を文字列へフォーマットする機能をサポートしている。これで非常に複雑な表現をすることもできるが、最も基本的な使い方は、1つのプレイスホルダを用いて文字列に値を挿入することだ。

>>> username = 'mark'
>>> password = 'PapayaWhip'                             
>>> "{0}'s password is {1}".format(username, password)  
"mark's password is PapayaWhip"
  1. いや、私の本当のパスワードはPapayaWhipじゃないよ。
  2. ここでは色々なことが起きている。第一に、これは文字列リテラルのメソッドを呼び出している。文字列はオブジェクトであり、オブジェクトはメソッドを持っているのだ。第二に、この式全体が評価された結果は文字列になる。第三に、{0}{1}は「置換フィールド」であり、これらはformat()メソッドに渡された引数によって置換されるのだ。

合成フィールド名

前の例は最も単純な場合であり、置換フィールドは単なる整数だった。整数の置換フィールドは、format()メソッドに渡した引数の位置を示すインデックスとして扱われる。{0}は1つ目の引数(この例ではusername)で置換され、{1}は2つ目の引数(この例ではpassword)で置換される。引数は望むだけいくつでも与えることができ、その引数の数だけインデックスを使うことができる。しかし、置換フィールドはこれよりもっと強力な機能を持っている。

>>> import humansize
>>> si_suffixes = humansize.SUFFIXES[1000]      
>>> si_suffixes
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
>>> '1000{0[0]} = 1{0[1]}'.format(si_suffixes)  
'1000KB = 1MB'
  1. humansizeモジュールの何らかの関数を呼び出すだけではなく、モジュールが定義しているデータ構造を取り出すこともできる: これはSI接頭語(1000のべき乗)のリストだ。
  2. これは複雑そうに見えるが、実際にはそうではない。{0}とすれば、format()関数に与えられた1つ目の引数、つまりsi_suffixesを指すことになる。しかしsi_suffixesはリストだ。したがって{0[0]}は、format()メソッドに1つ目の引数として与えられたリストの最初の要素、つまり'KB'を指している。それと同時に、{0[1]}は同じリストの2つ目の要素、つまり'MB'を指している。波括弧の外側にあるすべてのもの、つまり1000、イコール記号、空白などはすべてそのままにされる。最終的な結果として'1000KB = 1MB'という文字列が得られる。

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

びっくりさせるために、上記のものをすべて組み合わせた例を示そう:

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

この仕組みを説明すると次のようになる:

フォーマット指定子

待って! 話はまだ残っている! humansize.pyにあった変なコード行を、ここで見直してみよう:

if size < multiple:
    return '{0:.1f} {1}'.format(size, suffix)

{1}は、format()メソッドの2番目の引数suffixで置換される。しかし、{0:.1f}というのは何だろう? これは2つの部分からなる: {0}の部分は分かるが、:.1fの部分は一体何なのか。この後半部分(コロン以降)はフォーマット指定子というもので、置換する変数をどのようにフォーマットするのかをさらに詳細に指定するためのものだ。

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

置換フィールドの中では、コロン(:)はフォーマット指定子の開始を表す。フォーマット指定子“.1”は「最も近い小数第1位の数へ丸める」ことを表す(つまり小数点の後ろには数字を1つだけ表示する)。フォーマット指定子 "f" は「固定小数表記」にすることを表す(指数表記やその他の小数記法ではなく)。このようにして、たとえば698.24という値を持つsizeと、'GB'という値を持つsuffixを与えれば、698.24は小数第一位へ丸められ、そこに接尾語が付け足されることによって、フォーマット結果の文字列は'698.2 GB'になるだろう。

>>> '{0:.1f} {1}'.format(698.24, 'GB')
'698.2 GB'

フォーマット指定子の複雑な詳細のすべてを知りたい場合は、Python公式ドキュメントのFormat Specification Mini-Languageを参照して欲しい。

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

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

>>> s = '''Finished files are the re-  
... sult of years of scientif-
... ic study combined with the
... experience of years.'''
>>> s.splitlines()                     
['Finished files are the re-',
 'sult of years of scientif-',
 'ic study combined with the',
 'experience of years.']
>>> print(s.lower())                   
finished files are the re-
sult of years of scientif-
ic study combined with the
experience of years.
>>> s.lower().count('f')               
6
  1. Pythonの対話シェルで複数行文字列を入力できる。三重クォートで複数行文字列を開始して、 ENTERキーを押すと、対話シェルは次行の入力をうながしてくる。三重クォートで文字列を終了させ、ENTERキーを押すとコマンドが実行される(この例では文字列がsという変数に代入される)。
  2. splitlines()メソッドは、複数行の文字列を受け取って、その文字列の各行からなる文字列のリストを返す。各行の行末にある改行文字は含まれないことに注意しよう。
  3. lower()メソッドは文字列の全体を小文字に変換する(同様に、upper()メソッドは文字列を大文字に変換する)。
  4. count()は、部分文字列が文字列中に出現する回数を数える。そう、この文には6つの“f”が含まれているのだ。本当だよ!

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

>>> query = 'user=pilgrim&database=master&password=PapayaWhip'
>>> a_list = query.split('&')                                        
>>> a_list
['user=pilgrim', 'database=master', 'password=PapayaWhip']
>>> a_list_of_lists = [v.split('=', 1) for v in a_list if '=' in v]  
>>> a_list_of_lists
[['user', 'pilgrim'], ['database', 'master'], ['password', 'PapayaWhip']]
>>> a_dict = dict(a_list_of_lists)                                   
>>> a_dict
{'password': 'PapayaWhip', 'user': 'pilgrim', 'database': 'master'}
  1. 文字列のsplit()メソッドには、必ず引数として区切り文字を渡さなくてはならない。その区切り文字に基づいて、このメソッドは文字列をリストへ分割する。ここでの区切り文字はアンパサンド(&)だが、どんな文字列でも区切り文字として使える。
  2. さて、文字列のリストを手に入れた。各々の文字列には、キーがあり、その後ろにはイコール記号が続き、さらにその後ろに値が続く。リスト全体をイテレートして、各々の文字列を1つ目のイコール記号に基づいて2つに分割していくには、リスト内包表記が使える。split()メソッドの2番目の引数(オプション)は、 分割したい回数だ。1は「1回だけ分割する」ことを意味するので、split()メソッドは2つの要素をもつリストを返す。(理論上は、値がイコール記号を含んでいる可能性がある。もし、単純に'key=value=foo'.split('=')としていたら、結果として3つの要素を持つリスト['key'、'value'、'foo']が得られてしまうかもしれないのだ)。
  3. 最後に、リストのリストをdict()関数に渡すだけで、Pythonはそれを辞書に変換してくれる。

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

文字列をスライスする

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

>>> a_string = 'My alphabet starts where your alphabet ends.'
>>> a_string[3:11]           
'alphabet'
>>> a_string[3:-3]           
'alphabet starts where your alphabet en'
>>> a_string[0:2]            
'My'
>>> a_string[:18]            
'My alphabet starts'
>>> a_string[18:]            
' where your alphabet ends.'
  1. 文字列の一部を取り出すことができる。この操作は「スライス」と呼ばれ、2つのインデックスを指定することによって行う。戻り値は新しい文字列であり、1つ目のスライスインデックス以降の文字を、順番どおりに含んでいる。
  2. リストのスライスと同様に、文字列のスライスには負のインデックスが使える。
  3. 文字列のインデックスは0から始まるので、a_string[0:2]は最初の2文字、つまりa_string[0]からa_string[2]の直前までを含む。
  4. 左側のスライスインデックスが0のときはこれを省略できる。つまり、a_string[:18]a_string[0:18]と同じだ。
  5. 同様に、右側のスライスインデックスが文字列の長さと同じときはこれを省略できる。つまりa_string[18:]は、文字列の長さが44文字なのでa_string[18:44]と同じになる。ここには気持ちの良い対称性が存在する。この44文字の文字列では、a_string[:18]は最初の18文字を返し、a_string[18:]最初の18文字以外を返す。実際に、文字列の長さにかかわらず、a_string[:n]は常に最初のn文字を返すし、a_string[n:]はその残りを返すのだ。

文字列 vs. バイト列

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

>>> by = b'abcd\x65'  
>>> by
b'abcde'
>>> type(by)          
<class 'bytes'>
>>> len(by)           
5
>>> by += b'\xff'     
>>> by
b'abcde\xff'
>>> len(by)           
6
>>> by[0]             
97
>>> by[0] = 102       
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment
  1. bytesオブジェクトを定義するには、b''という「バイトリテラル」構文を使えばよい。バイトリテラルの各バイトには、ASCII文字と、16進数にエンコードされた\x00から\xffまでの数値 (0–255) が使える。
  2. bytesオブジェクトの型はbytesだ。
  3. リストや文字列と同様に、len()関数でbytesオブジェクトの長さを取得できる。
  4. リストや文字列と同様に、+演算子を使ってbytesオブジェクトを連結できる。結果は新しいbytesオブジェクトだ。
  5. 5バイトのbytesオブジェクトと1バイトのbytesオブジェクトを連結すると、6バイトのbytesオブジェクトが得られる。
  6. リストや文字列と同様に、インデックス記法によってbytesオブジェクトの個々のバイトを取り出せる。文字列の要素は文字列だが、bytesオブジェクトの要素は整数だ。厳密に言えば、0から255までの整数だ。
  7. bytesオブジェクトはイミュータブルだ。個々のバイトへ代入することはできない。もし個々のバイトを変更する必要があるときは、文字列スライスと結合演算子(これは文字列と同様に機能する)を使うこともできるし、bytesオブジェクトをbytearrayオブジェクトに変換することもできる。
>>> by = b'abcd\x65'
>>> barr = bytearray(by)  
>>> barr
bytearray(b'abcde')
>>> len(barr)             
5
>>> barr[0] = 102         
>>> barr
bytearray(b'fbcde')
  1. bytesオブジェクトを変更可能なbytearrayオブジェクトに変換するには、組み込みのbytearray()関数を使う。
  2. bytesオブジェクトで使えるすべてのメソッドと操作は、bytearrayオブジェクトでも同様に使える。
  3. 両者の1つの違いは、bytearrayオブジェクトではインデックス記法を使って個々のバイトへ代入できるのだ。代入する値は0から255までの整数でなければならない。

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

>>> by = b'd'
>>> s = 'abcde'
>>> by + s                       
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't concat bytes to str
>>> s.count(by)                  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'bytes' object to str implicitly
>>> s.count(by.decode('ascii'))  
1
  1. バイト列と文字列を連結することはできない。この2つは異なるデータ型なのだ。
  2. バイト列が文字列中に出現する回数を数えることはできない。文字列の中にバイト列というものは存在しないからだ。文字列は文字のシーケンスだ。 もしかすると「バイト列を特定の文字コードを使って文字列にデコードし、その文字列の出現回数を数えよ」という趣旨だったのかもしれないが、そうしたいのであれば、それを明示的に行う必要がある。Python 3が暗黙にバイト列を文字列に変換したり、文字列をバイト列に変換することはないのだ。
  3. 驚くべき偶然により、この行のコードは(英語で)次のような意味になっている。「このバイト列を指定した文字コードでデコードして得られる文字列の出現回数を数えよ」

文字列とバイト列のあいだの関係を示そう: bytesオブジェクトはdecode()メソッドを持っていて、これは文字コードを受け取って文字列を返す。文字列はencode()メソッドを持っていて、これは文字コードを受け取ってbytesオブジェクトを返す。前の例は、ASCIIコードのバイト列を文字列に変換するというもので、デコード処理は比較的単純だ。しかし同様の処理は、文字列に含まれている文字をサポートしているものなら、どんな文字コードでも(非Unicodeエンコーディングでも)動作する。

>>> a_string = '深入 Python'         
>>> len(a_string)
9
>>> by = a_string.encode('utf-8')    
>>> by
b'\xe6\xb7\xb1\xe5\x85\xa5 Python'
>>> len(by)
13
>>> by = a_string.encode('gb18030')  
>>> by
b'\xc9\xee\xc8\xeb Python'
>>> len(by)
11
>>> by = a_string.encode('big5')     
>>> by
b'\xb2`\xa4J Python'
>>> len(by)
11
>>> roundtrip = by.decode('big5')    
>>> roundtrip
'深入 Python'
>>> a_string == roundtrip
True
  1. これは文字列だ。9つの文字を含んでいる。
  2. これはbytesオブジェクトで、13バイトある。これはa_stringUTF-8でエンコードしたときに得られるバイト列だ。
  3. これはbytesオブジェクトで、11バイトある。これはa_stringGB18030でエンコードしたときに得られるバイト列だ。
  4. これはbytesオブジェクトで、11バイトある。これはa_stringBig5でエンコードしたときに得られるもので、先のものとは全く異なったバイト列になっている。
  5. これは文字列で、9つの文字がある。これはbyをBig5エンコーディングアルゴリズムでデコードしたときに得られる文字列だ。これは元の文字列に等しい。

追記: Pythonのソースコードの文字コード

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

Python2では、.pyファイルのデフォルトの文字コードはASCIIだった。Python3ではデフォルトの文字コードはUTF-8だ。

もしPythonのコードで異なる文字コードを使いたい場合は、文字コード宣言を各ファイルの先頭に書くことができる。次の宣言は、.pyファイルの文字コードをwindows-1252に設定している:

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

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

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

詳細は、PEP 263: Defining Python Source Code Encodingsを見てほしい。

もっと知りたい人のために

PythonでのUnicodeについて:

Unicode全般について:

他のフォーマットでの文字コードについて:

文字列と文字列のフォーマットについて:

© 2001– Mark Pilgrim
© Fukada, Fujimoto(日本語版)