Skip to content
This page has been auto-translated and may contain errors.View in English

文字列

テキストはほとんどすべてのプログラムに登場します。名前、メッセージ、スコア、ラベルなど。Pythonでは、テキストの一片は文字列と呼ばれます。引用符で囲んだ値はすべて文字列です。シングルクオートでもダブルクオートでも、どちらも同じように動作します。

文字列はPythonの主要なテキスト型です。ユーザー名からURLパス、整形された出力まで、あらゆるものを扱います。シングルクオートとダブルクオートは同一の結果を生み出します。選択はスタイルの問題です。

str はPythonの不変なUnicodeシーケンス型です。ターミナルI/O、ファイル内容、ネットワーク応答、シリアライズされたデータなど、あらゆるシステム境界に位置します。どちらの引用符スタイルも同じオブジェクトを生成し、トークナイザはそれらを同一に扱います。

python
greeting = "Hello, world"
username = 'タロウ'

引用符の選択が問題になるのは、テキストに引用符自体が含まれているときだけです。エスケープしなくて済むように、反対のスタイルを使いましょう:

コミュニティの慣例はダブルクオートです。スタイルを切り替える実用的な理由は、その文字が内容に含まれているときにエスケープを避けるためです:

慣例はダブルクオートです。スタイルを切り替える唯一の理由は、その区切り文字を内容に含むときにバックスラッシュによるエスケープを避けるためです:

python
note    = "It's a great day"      # アポストロフィを含むのでダブルクオートを使う
message = 'She said "hello"'      # ダブルクオートを含むのでシングルクオートを使う
escaped = "She said \"hello\""    # またはバックスラッシュでエスケープする

イミュータビリティ(不変性)

文字列は**不変(immutable)**です: 一度作成したら、変更することはできません。文字列は作られた瞬間に永久に固定されると考えてください。文字列を変更しているように見える操作は、実際には新しい文字列を生成しているのです。元の文字列はそのまま残ります。

文字列は**不変(immutable)**です: どのメソッドも文字列をその場で変更しません。テキストを変換するすべての操作は新しい文字列を返し、元のものはそのまま残します。実際的な結果として、どこにも代入しないメソッド呼び出しは何にも影響を与えません。

str オブジェクトは不変です: 内部バッファは構築時に固定され、書き込むことはできません。これにより文字列は3つの有用な特性を持ちます: ハッシュ可能(辞書のキーや集合の要素として有効)、コピーなしで参照間で安全に共有可能、そして短いリテラルに対するCPythonのインターン最適化の対象になることです。

python
name = "タロウ"
name = name.upper()   # "タロウ" は新しい文字列で、元の "タロウ" は変更されない

直接的な結果: 特定の位置の文字を変更することはできません。やろうとするとPythonはエラーを発生させます。

python
name = "タロウ"
name[0] = "ハ"   # TypeError: 'str' object does not support item assignment

変更された文字列を得るには、スライスやメソッドを使って新しい文字列を作ります。どちらも以下で扱います。

文字代入を試みると制約が直接見えます:

python
name = "タロウ"
name[0] = "ハ"   # TypeError: 'str' object does not support item assignment

変更されたバージョンが必要な場合、標準的な道具は、位置的な編集にはスライスと連結、置換には replace() です。どちらも新しい文字列を生成し、元のものはそのまま残します。

str.__setitem__ は実装されていません。要素代入は無条件に TypeError を発生させます。位置的な変更にはスライスを使います: name[:1].upper() + name[1:]。置換には replace() を使います。多くの断片を組み立てるには、"".join(parts) が重要です: ループ内で s += chunk を繰り返すのは O(n²) です。なぜなら各 + は結合長の新しいバッファを確保し、両オペランドをコピーするためです。join() は一度だけ確保します。

インデックスとスライス

文字列の各文字には、0から始まる番号付きの位置があります。その位置番号を角括弧に入れることで、個々の文字を読み取れます。負の数は末尾から逆向きに数えます。

文字列は0始まりのインデックスを持つシーケンスです。負のインデックスは末尾から数えます。スライスは単一の式で任意の連続した範囲を抽出でき、範囲外の値でもエラーを発生させません。

str は完全なシーケンスプロトコルを実装しています。添字アクセス (s[i]) は整数で __getitem__ を経由し、範囲外の入力に対して IndexError を発生させます。スライス (s[start:stop:step]) は slice オブジェクトを渡します。インデックスは静かに有効範囲にクランプされるため、スライスから IndexError が発生することはありません。

python
word = "Python"
#       012345

print(word[0])    # "P"
print(word[2])    # "t"
print(word[5])    # "n"
print(word[-1])   # "n"  (最後の文字)
print(word[-2])   # "o"  (最後から2番目)

-1 は常に最後の文字、-2 は最後から2番目、というように続きます。文字列の正確な長さを知らなくても末尾を取得したいときに便利です。

負のインデックスは折り返します: -1len(s) - 1-2len(s) - 2 です。長さを手動で計算したくないときの末尾基準アクセスに最も有用です。範囲外になる負のインデックスは、正のものと同様に IndexError を発生させます。

負のインデックスは境界チェックの前に len(s) + i に正規化されます。インタープリタに特別な処理はなく、単なる算術です。符号にかかわらず範囲外なら IndexError を発生させます。

スライスは塊を抽出します。[start:stop]start を含み、stop を除外します:

python
word = "Python"

print(word[0:2])   # "Py"     (位置 0 と 1)
print(word[2:])    # "thon"   (位置 2 から末尾まで)
print(word[:3])    # "Pyt"    (先頭から位置 2 まで)
print(word[:])     # "Python" (文字列全体のコピー)
print(word[::2])   # "Pto"    (1つおきの文字)
print(word[::-1])  # "nohtyP" (逆順)

よく使う3つのパターン: 最初のn文字には word[:n]、位置n以降には word[n:]、最後のn文字には word[-n:]word[::-1] は文字列を反転します。最初は奇妙に見えますが、Python の慣用句であり、よく目にすることになります。

直接のインデックスとは異なり、スライスは IndexError を発生させません。Pythonは範囲外のインデックスを静かにクランプするので、短い文字列に対する word[100:] はクラッシュではなく "" を返します。stepの引数はストライドを制御します: word[::2] は1つおきの文字を取り、word[::-1] は逆順に走査します。

s[start:stop:step]slice(start, stop, step)__getitem__ に渡します。3つの引数すべてのデフォルトは 0len() ではなく None です。負のstepでは、デフォルトが反転します: start のデフォルトは len - 1stop-(len + 1) です。これが [::-1] が明示的な境界なしに全文字列を逆順に走査できる理由です。

必須の文字列メソッド

文字列には組み込みのメソッドが用意されています: 任意の文字列値に対して直接呼び出せる操作です。文字列(またはそれを保持する変数)を書き、ドット、そしてメソッド名を続けます。各メソッドは新しい文字列を返します。元の文字列は決して変更されません。

文字列メソッドは str 型に紐付けられた関数です。文字列は不変なので、すべてのメソッドは元のものを変更するのではなく新しい文字列を返します。どこにも代入したり渡したりしないメソッド呼び出しは持続的な効果を持ちません。

str のメソッドは型オブジェクトに定義され、Cで実装されています。すべての変換メソッドは不変性の契約に従います: 新しい str オブジェクトを返します。CPythonの実装は全体を通してUnicode対応であり、メソッドはバイトではなくコードポイント単位で動作します。

大文字・小文字

python
text = "Hello, World"

text.lower()       # "hello, world"
text.upper()       # "HELLO, WORLD"
text.title()       # "Hello, World"  (各単語を大文字化)
text.capitalize()  # "Hello, world"  (最初の単語のみ)

lower()upper() が最もよく使う2つです。lower() はテキストを比較するときに特に便利です: "Alice""alice" は両方に .lower() を呼び出すと同じものになります。

lower() は比較や保存の前に行う標準的な正規化ステップです。title() はシンプルなルールで各単語の最初の文字を大文字にしますが、縮約形では誤動作します: "it's""It'S" になります。表示専用のフォーマットとして扱いましょう。

lower() はUnicodeフルケース変換を適用します。大文字・小文字を区別しない比較には、casefold() がより正しいです: lower() がスキップする追加の変換(例: ドイツ語のßがssになる)を適用します。title() は非英数字の後で大文字化するため、縮約形やハイフン付きの名前を誤って処理します。正しいタイトルケースには、ロジックを手動で実装してください。

空白

python
text = "  hello  "

text.strip()    # "hello"    (両側)
text.lstrip()   # "hello  "  (左のみ)
text.rstrip()   # "  hello"  (右のみ)

strip() は文字列の両端からスペースを削除します。ユーザー入力やファイルからのテキストを扱うときには必ずと言っていいほど使うでしょう。なぜなら、余分なスペースは静かな失敗を引き起こすからです: "alice" != "alice "

strip() は先頭と末尾のすべての空白(スペース、タブ、改行)を削除します。方向別のバリアントは片側だけをクリーンにでき、インデントに触れずに末尾の改行を削るのに便利です。3つすべては、特定の文字を削除するためのオプションの文字引数を受け取れます。

引数なしの strip()str.isspace()True を返す文字を削除します。これは非ASCIIの空白を含むUnicode対応の集合です。文字引数を指定すると、その集合に含まれる任意の文字を両端から削除します(プレフィックスマッチではなく、文字メンバーシップチェックです)。"xxhelloxx".strip("x")"hello" を返します。複数文字の引数はそれらの文字のいずれかを個別に削除し、これは微妙なバグの一般的な原因です。

検索

python
text = "Hello, world"

text.find("world")         # 7
text.find("Python")        # -1  (見つからない)
text.count("l")            # 3
text.startswith("Hello")   # True
text.endswith("world")     # True

find() はテキストの一片が文字列内のどこから始まるかという位置を返します。見つからなければ -1 を返します。文字列が何かで始まるか終わるかだけ気にしたいときには startswith()endswith() を使います。

find() は最初のマッチの開始インデックスか、-1 を返します。-1 の規約により、結果をチェックなしでスライスや算術に直接使えます。startswith()endswith() はそれぞれ文字列のタプルを受け取れるため、複数のプレフィックスやサフィックスを1回の呼び出しでテストするのが簡単です。

find() は左から右への線形スキャンで、最悪ケースは O(n*m) です。index() は同一ですが、マッチがない場合に ValueError を発生させます: 不在がプログラミングエラーである場合は index() を、予期される入力である場合は find() を使います。startswith()endswith() は最初の不一致で短絡し、プレフィックス・サフィックステストには find()in チェックより高速です。

置換

python
text = "Hello, world"

text.replace("world", "Python")   # "Hello, Python"
text.replace("l", "L")            # "HeLLo, worLd"  (すべての出現箇所)
text.replace("l", "L", 1)         # "HeLlo, world"  (最初のみ)

replace() はテキストのすべての出現箇所を別のものに置き換え、新しい文字列を返します。元のものは変更されません。最初の出現だけを置換したい場合は第3引数を渡します。

replace() はデフォルトで重複しないすべての出現箇所を置換します。countの引数で置換数を上限指定できます。新しい文字列を返すので、呼び出しを連鎖できます: text.replace("a", "A").replace("e", "E") は両方の置換を順に適用します。

replace() は文字どおりの部分文字列スキャンを実行し、countが指定されていない場合は単一のアロケーションで結果を構築します。countが指定されている場合は早期に停止します。パターンベースの置換には、Pythonの re モジュールが適切なツールです。これはモジュールの章で扱います。

分割と結合

split() は文字列を区切り文字で複数の部分に切り分け、リストとして返します。何で切るかを指定します:

split() は区切り文字で分割し、セグメントをリストとして返します。引数なしで呼び出すと、任意の連続する空白で分割し、複数の連続するスペースから生じる空文字列を捨てます:

split(sep) は左から右へスキャンし、sep の重複しないすべての出現箇所で分割します。引数なしの場合は異なるアルゴリズムを使います: 連続する空白で分割し、結果から先頭と末尾の空白を取り除きます。rsplit(sep, n) は右から分割し、ドット区切りのパスや名前空間付き識別子の最後のセグメントを分離するのに便利です:

python
csv_row = "タロウ,28,東京"
parts = csv_row.split(",")     # ["タロウ", "28", "東京"]

"  hello   world  ".split()   # ["hello", "world"]

リストとは?

リストは値の順序付きコレクションです。上の ["タロウ", "28", "東京"] がそれです。リストには独自の章がありますが、今のところは split() が生成し join() が消費する項目のシーケンスとして扱ってください。

join() はその逆を行います: 文字列のリストを1つに結合します。.join() の前の文字列が各項目の間に置かれます:

python
words = ["Hello", "world"]

" ".join(words)    # "Hello world"
", ".join(words)   # "Hello, world"
"".join(words)     # "Helloworld"

覚えるべきパターン: separator.join(list_of_strings)。区切り文字は左、リストは右です。" ".join(words) は各単語の間にスペースを入れます。"".join(words) は何もなしでくっつけます。

複数の断片から単一の文字列を組み立てるときには join() が適切なツールです。各ステップで新しい文字列を作るのではなく、単一のアロケーションを行います。2、3個の文字列であれば + で十分です。ある程度の大きさのリストがあれば、join() を使いましょう。

join() は O(n) です: __iter__ を1回呼び出し、1パスで必要な合計長を計算し、単一のアロケーションを実行し、その後各断片と区切り文字をバッファに直接書き込みます。繰り返しの + は O(n²) です: 各操作は結合長の新しいバッファを確保し、両オペランドをコピーします。CPythonは単一のローカル変数に対する繰り返しの += に対して限定的な最適化を持っていますが、これはリファクタリングをまたぐと脆く、保証されていません。join() は常に正しく、常に高速です。

f-string

f-stringはテキストの中に直接値を埋め込みます。開きクオートの前に f を付けて、変数や式を波括弧で囲みます。コードが実行されるときにPythonが埋め込みます。値の後ろにコロンを付けて、表示の仕方を制御することもできます。

f-stringは {} 内の任意の式を実行時に評価し、結果を文字列に変換します。波括弧内のコロンはフォーマットスペックを導入します: 小数点以下の桁数、配置、数値フォーマットを制御するためのコンパクトな構文です。

f-string(PEP 498)は各 {} 式を、format(value, spec) を呼び出すバイトコードにコンパイルします。これは value.__format__(spec) に委譲します。__format__ を実装するクラスは、f-string内での自身の表示を制御します。変換フラグ !r!s!a はフォーマット呼び出しの前に repr()str()ascii() を適用します。

python
name  = "タロウ"
score = 94.5

print(f"Hello, {name}!")           # "Hello, タロウ!"
print(f"Score: {score:.1f}%")      # "Score: 94.5%"
print(f"2 + 2 = {2 + 2}")          # "2 + 2 = 4"
print(f"Name: {name.upper()}")     # "Name: タロウ"

: の後のフォーマットスペックは値の表示方法を制御します:

スペック意味
.2f小数点以下2桁f"{3.14159:.2f}""3.14"
.0%パーセンテージ、小数なしf"{0.94:.0%}""94%"
,桁区切りf"{1000000:,}""1,000,000"
>1010文字幅で右寄せf"{'hi':>10}"" hi"

最もよく使うのは .2f です: 長い桁数ではなくきれいな数字を表示したいときはいつでも。表内の他のものは必要なときに使えます。{} 内には任意の変数、算術、メソッド呼び出しを入れることができます。

.2f.0% は表示フォーマットの大半をカバーします。配置指定子 (><^) は幅と組み合わせることで表形式の出力を生成します。一般的なパターンは {value:[align][width][.precision][type]} です。各部分を認識できるようになれば、すべての組み合わせを暗記しなくても任意のスペックを読めるようになります。

スペックはそのまま __format__ に渡されます。組み込み型はそれをCで処理します。!r は最も有用な変換フラグです: フォーマット前に repr() を呼び出し、文字列の周りに引用符を追加して、不可視文字(タブ、末尾のスペース、改行)をエスケープシーケンスとして可視化します。カスタムクラスは __format__ を実装することで任意のスペック文字列を受け取り、任意の出力を生成できます。

複数行文字列

複数行にわたる文字列を書くには、三重引用符を使います: 始まりに " を3つ、終わりに3つです。Pythonは書いたとおりにすべての改行と空白を保持します。

三重引用符付き文字列は、すべての空白と改行を文字どおりに保持します。長いテキストブロック(電子メールテンプレートやSQLクエリなど)や、ドキュメンテーション文字列(関数やクラス本体の先頭に置かれるインラインドキュメント)で標準的に使われます。

三重引用符付きリテラルは、各行の先頭の空白を含むすべての文字を文字どおりに保持します。関数、クラス、モジュール本体の最初の文として使われると、Pythonはその文字列をそのオブジェクトの __doc__ 属性として格納します。help() などのツールがそれを表示し、先頭の空白は通常 textwrap.dedent() で取り除かれます。三重 '''""" は等価ですが、""" が慣例です。

python
message = """
タロウ様、

ご注文ありがとうございます。

敬具、
チーム一同
"""

エスケープシーケンス

一部の文字は文字列内で直接タイプするのが難しいです。Pythonはエスケープシーケンスを使います: 何かを表す文字が続くバックスラッシュです。常に使う2つは、改行のための \n とタブのための \t です。

エスケープシーケンスは、構文を壊してしまう文字や直接タイプできない文字を埋め込むことを可能にします。よく使うもの: \n(改行)、\t(タブ)、\\(リテラルなバックスラッシュ)、\"\'(一致する区切り文字の文字列内の引用符)。Windowsのパスはバックスラッシュを要求し、これはエスケープ処理と衝突します。r プレフィックスで無効化できます。

PythonはC形式のエスケープセットに加えてUnicodeエスケープをサポートします: \uXXXX(16ビットコードポイント)、\UXXXXXXXX(32ビット)、\xNN(16進数バイト値)、\N{name}(名前付きUnicode文字)。生文字列リテラル(r"...")はすべてのエスケープ処理を抑制し、すべてのバックスラッシュをそのまま文字列に渡します。これはWindowsのパスや正規表現には不可欠で、そこではバックスラッシュがPythonのトークナイザではなく消費側に意味を持ちます。

シーケンス文字
\n改行
\tタブ
\\リテラルなバックスラッシュ
\"ダブルクオート
\'シングルクオート
python
print("Line one\nLine two")        # 出力は2行
print("Name:\tAlice")              # Name:   Alice
path = r"C:\Users\Alice\Documents" # 生文字列、エスケープ処理なし

文字列内容のチェック

Pythonには文字列の内容についてyes/noの質問に答えるメソッドがあります。TrueFalse を返します。早い段階で最も有用なもの: isdigit() を使うと、文字列が全部数字かどうか変換前にチェックでき、予期しない入力でのクラッシュを避けられます。

is* メソッドはそれぞれ文字列全体の特定の特性をテストし、すべての文字が条件を満たす場合にのみ True を返します。主な用途は入力検証です: 変換前にチェックして予期しない入力でのクラッシュを避けます。int() の前の isdigit() は古典的なパターンです。

is* メソッドはASCII範囲ではなく、Unicodeカテゴリチェックを使います。isdigit()0-9 を超える上付き文字や他の数値Unicodeコードポイントに対して True を返します。厳密なASCII数字チェックには、s.isascii() and s.isdigit() を組み合わせます。isnumeric() はさらに広く、分数や数値値を持つUnicode文字をカバーします。手を伸ばす前に、実際にどれが必要かを知っておきましょう。

python
"42".isdigit()       # True
"hello".isalpha()    # True
"hello42".isalnum()  # True
"   ".isspace()      # True
"Hello".islower()    # False
"HELLO".isupper()    # True

実践例

空白を取り除き、ケースを正規化し、必要なものを取り出す。このシーケンスはほとんどあらゆるユーザー提供のテキストを処理します:

python
raw_input = "  [email protected]  "
email     = raw_input.strip().lower()   # "[email protected]"

at_pos   = email.find("@")
username = email[:at_pos]
domain   = email[at_pos + 1:]

print(f"User:   {username}")    # "taro"
print(f"Domain: {domain}")      # "example.com"

部品からURLを構築し、即座に検証して解析する:

python
BASE_URL = "https://api.example.com"
version  = "v2"
resource = "users"
user_id  = 42

url      = f"{BASE_URL}/{version}/{resource}/{user_id}"
# "https://api.example.com/v2/users/42"

protocol = url.split("://")[0]                    # "https"
secured  = url.startswith("https")
domain   = url.split("://")[1].split("/")[0]      # "api.example.com"

print(f"Protocol : {protocol}")
print(f"Secure   : {secured}")
print(f"Domain   : {domain}")

find()、スライス、f-stringの整列を使って構造化されたログ行を解析する:

python
log_entry = "[2024-01-15 09:42:11] ERROR: File not found: report.csv"

timestamp = log_entry[1:20]
rest      = log_entry[22:]                # "ERROR: File not found: report.csv"
colon_pos = rest.find(":")
level     = rest[:colon_pos]              # "ERROR"
message   = rest[colon_pos + 2:]          # "File not found: report.csv"

print(f"[{timestamp}] {level:>8}: {message}")
# [2024-01-15 09:42:11]    ERROR: File not found: report.csv

find() が境界を特定し、スライスが部分を抽出し、>8 フォーマットスペックが重大度ラベルを右寄せにして、レベル名の長さが異なってもカラムが一貫した状態を保ちます。

メソッドリファレンス

メソッド動作
.lower() / .upper()すべて小文字 / すべて大文字に変換
.title() / .capitalize()各単語を大文字化 / 最初のみ
.strip() / .lstrip() / .rstrip()周囲の空白を削除
.find(sub)最初のマッチのインデックス、または -1
.count(sub)sub が現れる回数
.startswith(s) / .endswith(s)プレフィックス / サフィックスのチェック
.replace(old, new)出現箇所を置換
.split(sep)リストに分割
sep.join(iterable)項目を文字列に結合
.isdigit() / .isalpha() / .isalnum()文字種別チェック