文字列の発展
レッスン 06 では基本を学びました。今回はさらに深く掘り下げます。イミュータビリティとは実際にはどういう意味でしょうか?あまり知られていないが強力な文字列操作にはどんなものがあるでしょうか?中国語の文字はコンピュータにどのように保存されるのでしょうか?このレッスンが終われば、文字列に対する理解が新たなレベルに達します。
1. 文字列のイミュータビリティ
レッスン 06 の FAQ で簡単に触れました——文字列は一度作成されると変更できません。これが具体的に何を意味するか見てみましょう。
text = "Hello"
# ❌ このように修正できない — エラーになる
# text[0] = "h" # TypeError: 'str' object does not support item assignment
最初の文字を小文字に変更するには、新しい文字列を作成します:
text = "Hello"
new_text = "h" + text[1:] # 新しい文字列を作成
print(new_text) # hello
print(text) # Hello — 元の文字列は変わらない
イミュータビリティの利点
# 安全な共有参照
a = "Python"
b = a # b と a は同じ文字列を指す
# a に何をしても b は影響を受けない
a = a.upper()
print(a) # PYTHON
print(b) # Python — b は元の値を保持
イミュータビリティの代償
「変更」するたびに新しいオブジェクトが作成されます。ループ内で多数の文字列を連結すると遅くなります:
# ❌ 推奨しない:ループ内での文字列連結
result = ""
for i in range(10000):
result += str(i) + ","
# これにより 10000 個の一時的な文字列オブジェクトが作成される
# ✅ 推奨:リストに集めてから join()
parts = []
for i in range(10000):
parts.append(str(i))
result = ",".join(parts)
+= で全く問題ありません。
例:文字列メソッドのシミュレーション(難易度 ⭐⭐)
# 「capitalize」関数を実装
def my_capitalize(text):
if not text:
return text
first = text[0].upper()
rest = text[1:].lower()
return first + rest
print(my_capitalize("hello WORLD")) # Hello world
print(my_capitalize("python")) # Python
# 「replace」関数を実装
def my_replace(text, old, new):
parts = text.split(old) # old で分割
return new.join(parts) # new で再結合
print(my_replace("one, two, one", "one", "1")) # 1, two, 1
2. 文字列クエリメソッド
レッスン 06 の find() と count() に加えて、さらに便利なメソッドを紹介します。
startswith() と endswith()
# 文字列の開始/終了を確認
url = "https://www.example.com"
print(url.startswith("https")) # True — HTTPS プロトコル
print(url.endswith(".com")) # True — .com ドメイン
print(url.endswith(".org")) # False
# 複数のパターンを一度にチェック可能
print(url.endswith((".com", ".org", ".cn"))) # True — .com にマッチ
実用的な使い方
# ファイル拡張子のチェック
filename = "report.pdf"
if filename.endswith((".pdf", ".doc", ".docx")):
print("Document file")
elif filename.endswith((".jpg", ".png", ".gif")):
print("Image file")
elif filename.endswith((".py", ".js", ".html")):
print("Code file")
else:
print("Other file")
# URL プロトコルのチェック
link = "ftp://files.example.com"
if link.startswith(("http://", "https://")):
print("Web link")
elif link.startswith("ftp://"):
print("FTP link")
出力:
Document file
FTP link
removeprefix() と removesuffix()(Python 3.9+)
これらは Python 3.9 で追加され、プレフィックスやサフィックスをきれいに除去します:
url = "https://www.example.com"
# プロトコルプレフィックスを除去
domain = url.removeprefix("https://")
print(domain) # www.example.com
# ドメインサフィックスを除去
site_name = url.removesuffix(".com")
print(site_name) # https://www.example
# 文字列が指定されたテキストで始まっていない/終わっていない場合は、そのまま返す
print(url.removeprefix("ftp://")) # https://www.example.com(変更なし)
url[len("https://"):] や url.split("://", 1)[1] が必要でした。removeprefix() と removesuffix() により、より直感的になりました。
3. 高度な split() の使い方
レッスン 06 では基本的な分割を学びました。さらに強力なバリエーションを探ってみましょう。
分割数の制限
# maxsplit パラメータで分割数を制限
text = "one,two,three,four,five"
print(text.split(",", 1)) # ['one', 'two,three,four,five']
print(text.split(",", 2)) # ['one', 'two', 'three,four,five']
print(text.split(",", 3)) # ['one', 'two', 'three', 'four,five']
rsplit():右から左への分割
text = "one,two,three,four,five"
print(text.rsplit(",", 1)) # ['one,two,three,four', 'five']
print(text.rsplit(",", 2)) # ['one,two,three', 'four', 'five']
splitlines():行ごとの分割
multiline = """First line
Second line
Third line"""
lines = multiline.splitlines()
print(lines) # ['First line', 'Second line', 'Third line']
# 改行を保持
lines = multiline.splitlines(True)
print(lines) # ['First line\n', 'Second line\n', 'Third line']
partition() と rpartition()
partition() は分割するだけでなく、区切り文字の位置も保持します:
text = "user@example.com"
# @ で分割して 3 つの部分に:(左、区切り、右)
parts = text.partition("@")
print(parts) # ('user', '@', 'example.com')
# 区切り文字が見つからない場合、('元の文字列', '', '') を返す
print("hello".partition("@")) # ('hello', '', '')
# rpartition は右から検索
path = "/home/user/documents/file.txt"
last_slash = path.rpartition("/")
print(last_slash) # ('/home/user/documents', '/', 'file.txt')
partition() は「抽出」シナリオで split() よりも優れています——3 つの部分がそれぞれ何であるかを正確に示してくれます。
例:URL の解析(難易度 ⭐⭐)
# partition を使って URL を解析
url = "https://www.example.com:8080/path/page.html?name=test&page=1"
# プロトコルを抽出
proto, _, rest = url.partition("://")
print(f"Protocol: {proto}") # https
# ホストとパスを抽出
host_part, _, path_and_query = rest.partition("/")
print(f"Host: {host_part}") # www.example.com:8080
# ポートを抽出
host, _, port = host_part.partition(":")
print(f"Domain: {host}") # www.example.com
print(f"Port: {port}") # 8080
# パスとクエリを抽出
path, _, query = path_and_query.partition("?")
print(f"Path: /{path}") # /path/page.html
print(f"Query: {query}") # name=test&page=1
出力:
Protocol: https
Host: www.example.com:8080
Domain: www.example.com
Port: 8080
Path: /path/page.html
Query: name=test&page=1
4. 文字列の配置とパディング
Python は指定した幅の中で文字列を配置するメソッドを提供します——左詰め、右詰め、中央揃え。
text = "Python"
print(text.ljust(10)) # 'Python '(左詰め、右側にスペース)
print(text.rjust(10)) # ' Python'(右詰め、左側にスペース)
print(text.center(10)) # ' Python '(中央揃え、両側にスペース)
# 埋め文字を指定可能
print(text.ljust(10, "-")) # 'Python----'
print(text.rjust(10, "-")) # '----Python'
print(text.center(10, "-")) # '--Python--'
zfill():ゼロ埋め
数値 ID によく使われます:
print("42".zfill(5)) # 00042
print("-42".zfill(5)) # -0042 — 負号が先頭
print("3.14".zfill(8)) # 00003.14
# 3 桁の ID を生成
for i in range(1, 11):
filename = f"photo_{str(i).zfill(3)}.jpg"
print(filename, end=" ")
出力:
photo_001.jpg photo_002.jpg photo_003.jpg photo_004.jpg photo_005.jpg
photo_006.jpg photo_007.jpg photo_008.jpg photo_009.jpg photo_010.jpg
例:配置したレポートの生成(難易度 ⭐⭐)
# rjust と ljust を使って表を配置
items = [
("Apple", 5.0, 3),
("Banana", 3.5, 5),
("Milk", 12.0, 2),
]
# ヘッダー
header_name = "Item".ljust(8)
header_price = "Price".rjust(6)
header_qty = "Qty".rjust(4)
header_total = "Total".rjust(6)
print(f"{header_name}{header_price}{header_qty}{header_total}")
print("-" * 28)
# データ行
for name, price, qty in items:
total = price * qty
name_col = name.ljust(8)
price_col = f"{price:.1f}".rjust(6)
qty_col = str(qty).rjust(4)
total_col = f"{total:.1f}".rjust(6)
print(f"{name_col}{price_col}{qty_col}{total_col}")
出力:
Item Price Qty Total
────────────────────────────
Apple 5.0 3 15.0
Banana 3.5 5 17.5
Milk 12.0 2 24.0
5. 文字エンコーディング:ord() と chr()
コンピュータ内のすべての文字は数値(コードポイント)に対応しています。ord() は文字の数値エンコーディングを取得し、chr() はその逆を行います。
# Unicode コードポイントを表示
print(ord("A")) # 65
print(ord("中")) # 20013
print(ord("❤")) # 10084
# コードポイントから文字を取得
print(chr(65)) # A
print(chr(20013)) # 中
print(chr(10084)) # ❤
# すべての大文字英字を生成
for code in range(ord("A"), ord("Z") + 1):
print(chr(code), end=" ")
# 出力:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Unicode とは何か
簡単に言うと、Unicode は世界共通の文字エンコーディング標準です——世界中の書記体系(中国語、英語、アラビア語、日本語など)のすべての文字に一意の番号を割り当てています。Python 3 はすべての文字列を内部で Unicode として保存するため、複数の言語をネイティブにサポートしています。
# Python 3 の文字列はネイティブに Unicode をサポート
text = "Hello, 你好, مرحبا, こんにちは"
print(text) # すべて正しく表示される
print(len(text)) # 文字数
# \u エスケープで文字を表現することもできる
print("\u0048") # H
print("\u4e2d\u6587") # 中文(中国語)
str(バイト)と unicode(テキスト)に分かれており、エンコーディングに関するエラーが頻発していました。Python 3 ではこれらが統合されました——すべての文字列は Unicode であり、中国語と英語を同等に扱います。これは Python 3 の最も優れた設計上の決定の 1 つです。
エンコードとデコード
Python は内部で Unicode を使用しますが、ファイル保存やネットワーク送信ではバイトが必要です。そのためエンコード(encode)とデコード(decode)が必要です:
text = "Hello, 世界"
# エンコーディング:文字列 → バイト
utf8_bytes = text.encode("utf-8")
print(utf8_bytes) # b'\xe4\xbd\xa0\xe5\xa5\xbd...'
print(len(utf8_bytes)) # UTF-8 でのバイト数
gbk_bytes = text.encode("gbk")
print(len(gbk_bytes)) # GBK でのバイト数
# デコーディング:バイト → 文字列
decoded = utf8_bytes.decode("utf-8")
print(decoded) # Hello, 世界
# エンコードとデコードが一致しないとエラーになる
# gbk_bytes.decode("utf-8") # UnicodeDecodeError!
encoding="utf-8" を指定すれば解決します。
6. 文字列書式設定の補足
f-string 以外に、Python には 2 つの古い書式設定方法があります。f-string が現代の標準ですが、古いコードでこれらに遭遇することがあります。
format() メソッド(Python 3.0+)
# 位置による指定
print("My name is {}, I'm {} years old.".format("Alice", 18))
# My name is Alice, I'm 18 years old.
# 名前による指定
print("My name is {name}, I'm {age} years old.".format(name="Bob", age=22))
# My name is Bob, I'm 22 years old.
# 数値の書式設定
print("Pi: {:.3f}".format(3.14159))
# Pi: 3.142
% 書式設定(Python 2 時代)
# 古いコードで見かけることがある
print("My name is %s, I'm %d years old." % ("Alice", 25))
# My name is Alice, I'm 25 years old.
print("Pi: %.2f" % 3.14159)
# Pi: 3.14
format() は「遅延書式設定」(テンプレートを先に定義し、後で値を埋める)にまだ便利です。% 書式設定は遭遇したときに認識できるようにするためだけのもので、新しいコードでは使わないでください。
よくあるユースケース
- バッチファイル名変更:
endswith()でファイルタイプをフィルタリング、zfill()で統一番号付け - URL 解析:
partition()とremoveprefix()でプロトコル、ドメイン、パスを抽出 - CSV データ処理:
split()のmaxsplitで分割数を制御、フィールド内のカンマ干渉を回避 - ログ書式設定:
center()とljust()で配置されたログ出力 - エンコーディング変換:異なるソースからのテキストファイルを
encode()とdecode()で処理 - データ検証:
startswith()とendswith()で形式をチェック(メール、電話、リンク)
❓ よくある質問
bytes 型で別途処理されます。これにより文字列操作が直感的になります——len("中") はどのシステムでも常に 1 です。トレードオフとして、ファイルやネットワーク I/O では手動での encode/decode が必要です。split() と partition() はいつ使い分ければよいですか?
A:単純な分割には split() を使います。区切り文字そのものが必要な場合は partition() を使います。partition() は常に 3 つの要素(左、区切り、右)を返すため、タプルアンパックに便利です:left, sep, right = text.partition(":")。split() は可変長のリストを返します。区切り文字が 1 回だけ出現し、両側の内容が必要な場合は、partition() が最適です。
ljust() と f-string の {value:10} の違いは何ですか?text.ljust(10) は f"{text:<10}" と同じです。f-string 版はより柔軟(配置と数値書式設定を組み合わせられる)ですが、メソッド呼び出しの方が直感的です。好みの問題です——1 つ選んで一貫して使いましょう。f-string の配置記号:< 左詰め、> 右詰め、^ 中央揃え:f"{text:^10}"。📖 まとめ
- 文字列はイミュータブル:各「変更」は新しいオブジェクトを作成。大量連結では
+=よりjoin()を使用 - クエリメソッド:
startswith()/endswith()で開始/終了をチェック。タプル引数で複数チェック可能 - Python 3.9+:
removeprefix()/removesuffix()できれいにプレフィックス/サフィックスを除去 - 高度な分割:
split(maxsplit=N)で制限、rsplit()で右から、splitlines()で行ごと partition()は区切り情報を保持、構造化解析に最適- 配置:
ljust()/rjust()/center()/zfill()。カスタム埋め文字をサポート ord()は文字の Unicode コードポイントを取得。chr()はその逆- Python 3 の文字列は内部で Unicode を使用。encode/decode は
encode()/decode()。UTF-8 推奨
📝 練習問題
-
基本(難易度 ⭐):
files = ["report.pdf", "photo.jpg", "script.py", "notes.txt", "index.html"]として、endswith()を使ってフィルタリングしてください:- すべての画像ファイル(.jpg, .png, .gif)
- すべてのドキュメントファイル(.pdf, .doc, .docx, .txt)
- すべてのコードファイル(.py, .js, .html, .css)
-
中級(難易度 ⭐⭐):「バッチファイル名変更プログラム」を書いてください。
photos = ["IMG_1.jpg", "IMG_2.jpg", ..., "IMG_12.jpg"]をphoto_001.jpg、photo_002.jpg、...、photo_012.jpgに変更します。ヒント:partition()でファイル名と拡張子を分割、zfill()でゼロ埋め。 -
挑戦(難易度 ⭐⭐⭐):「簡易テンプレートエンジン」を書いてください。テンプレート文字列
templateと辞書dataが与えられ、{variable}プレースホルダを実際の値に置き換えます。PYTHONtemplate = "Dear {name}, your order {order_id} has been {status}. Estimated delivery: {delivery_time}." data = { "name": "Alice", "order_id": "20260623001", "status": "shipped", "delivery_time": "3 business days" } # あなたのコード:replace_template(template, data) を実装 # 出力:Dear Alice, your order 20260623001 has been shipped. Estimated delivery: 3 business days.追加要件:テンプレート変数が data に存在しない場合、
{variable}はそのままにします(置換しない)。ヒント:data のキーと値のペアを反復処理し、テンプレートに対してそれぞれreplace()を呼び出します。



