Python | 文字列の応用テクニック/落とし穴/実戦で役立つ小ワザ

Python
スポンサーリンク

重箱の隅をつつくような 文字列の応用テクニック/落とし穴/実戦で役立つ小ワザ を、短い解説+実行可能なコード例付きでまとめます。初心者〜中級者が「知ってて得する」「知らないとハマる」ポイント中心にします。

① Unicode 正規化(NFC / NFD)

日本語や絵文字で見た目は同じでも内部表現が異なることがあり、比較でハマります。unicodedata.normalize() を使って正規化しましょう。

import unicodedata

a = "é"                # 単一文字(é)
b = "e\u0301"          # e + 結合アクセント
print(a == b)          # False

a_n = unicodedata.normalize("NFC", a)
b_n = unicodedata.normalize("NFC", b)
print(a_n == b_n)      # True
Python

なぜ大事?ユーザー入力比較・重複削除・検索で差異を防げます。


② 大文字小文字比較は casefold() を使う

単純な lower() より強力で、ドイツ語 ß などのケースも扱えます。

s1 = "Straße"
s2 = "STRASSE"
print(s1.lower() == s2.lower())      # False(場合あり)
print(s1.casefold() == s2.casefold())# True
Python

③ 文字数と「見た目の長さ(グラフェム)」は違う

日本語や絵文字は一見1文字でも複数コードポイントに分かれていることがある。正確な「ユーザーが見る文字数」は regex\X(第三者ライブラリ)で数えるのが安全。標準では難しいことに注意。

(簡易示唆)絵文字を扱うなら外部ライブラリ/正規表現パッケージ検討を。


④ バイト列と文字列(bytes vs str)

ファイル/ネットワークはバイト列。エンコード・デコードを明示的に。

s = "こんにちは"
b = s.encode("utf-8")   # bytes
s2 = b.decode("utf-8")
Python

注意:間違ったエンコーディングで decode すると UnicodeDecodeError


⑤ BOM(Byte Order Mark)に注意 — ファイル入力時

Windows 系の CSV/Excel 出力で先頭に BOM がつくことがある(\ufeff)。lstrip("\ufeff")encoding="utf-8-sig" で対処。

s = "\ufeff名前,年齢\n山田,30"
print(s.lstrip("\ufeff"))
# ファイル読み込みなら open(..., encoding="utf-8-sig")
Python

⑥ 高速な連結/繰り返し:join() vs +

大量の文字列を繰り返し連結する場合、+ をループで使うと遅い。リストに貯めて ''.join(list)

# NG(遅い)
s = ""
for i in range(10000):
    s += str(i)

# OK(速い)
parts = []
for i in range(10000):
    parts.append(str(i))
s = "".join(parts)
Python

⑦ f-string の高度なフォーマット(数値&幅揃え)

f"{value:>10,.2f}" のように書けます。

x = 12345.6789
print(f"{x:>12,.2f}")  # 桁区切り+小数点2桁+右寄せ幅12
Python

注意:ユーザー入力をそのままフォーマット文字列に渡すとフォーマット注入の危険があります。テンプレートは安全な方法で扱う。


⑧ re.sub() に関数を渡す — 複雑な置換で威力発揮

置換内容がマッチごとに動的に変わるときは、関数を使います。

import re

def repl(m):
    word = m.group(0)
    return word.upper()

text = "apple banana apple"
print(re.sub(r"apple", repl, text))  # APPLE banana APPLE
Python

実用例:日付フォーマットを捕まえて標準化する、URL を一部マスクする、など。


⑨ 非表示文字(ゼロ幅スペース)に要注意

見た目では見えない文字(U+200Bなど)が混入すると比較や表示でバグります。入力を正規化して除去することが有効。

s = "a\u200Bb"
print(len(s))          # 3 (見た目は2文字)
# 除去
s_clean = s.replace("\u200b", "")
Python

⑩ HTML/シェル/SQL に対するエスケープ(安全な出力)

ユーザー入力をそのまま HTML に出すと XSS、SQL に入れると SQL Injection。Webなら html.escape()、SQL ならプレースホルダ(? / %s)を使う。

import html
raw = '<script>alert(1)</script>'
print(html.escape(raw))  # <script>alert(1)</script>
Python

(SQL の場合はライブラリのパラメタライズを使う。生文字列連結は✖️)


⑪ CSV を素直に split(",") してはいけない

"a","b, c",d のようにカンマがフィールド内にあると分割できません。csv モジュールを使いましょう。

import csv
from io import StringIO

row = '"a","b, c",d'
reader = csv.reader(StringIO(row))
print(next(reader))  # ['a','b, c','d']
Python

⑫ 正規表現の貪欲性(Greedy)と非貪欲(?)

.* は貪欲で最大マッチします。HTML のタグ抜きや範囲マッチでハマります。必要に応じて .*?(最短)を使う。

import re
s = "<p>one</p><p>two</p>"
print(re.findall("<p>.*</p>", s))   # ['<p>one</p><p>two</p>']
print(re.findall("<p>.*?</p>", s))  # ['<p>one</p>', '<p>two</p>']
Python

⑬ 不変性(immutable)を利用した安全な操作

文字列は不変なので、部分書き換えは常に新しい文字列が生成されます。大量の局所的な書換えはメモリ/速度面で考慮が要るので list() にして操作→戻すのが実用的。

s = "abcd"
lst = list(s)
lst[1] = "Z"
s2 = "".join(lst)  # 'aZcd'
Python

⑭ CSV/TSV/JSON などでの文字列エンコーディング運用ルール

  • ファイルは可能な限り UTF-8 に統一する(encoding="utf-8")。
  • 外部データは BOM や異なるエンコーディングを想定して読み込む(utf-8-sig等)。
  • JSON は ensure_ascii=False を使うと日本語がエスケープされず見やすく書ける。
import json
data = {"name": "山田"}
print(json.dumps(data, ensure_ascii=False))
Python

⑮ 小ネタ:部分一致を効率化(トライ/Set 利用)

多数のパターン(キーワード)を対象に「どれか含まれるか」をよく調べるなら、any(k in s for k in keywords) より高速な手法(Aho-Corasick ライブラリ)を検討。


練習問題(短め) — 自分で実行してみよう

  1. "e\u0301""é" を正規化して等価にする短いコードを書け。答えは上の①を参照。
  2. s = " apple, banana , cherry " を CSV のように分解して要素の前後空白を取り除いたリストにせよ(splitstrip を使う)。
  3. "<b>bold</b><b>text</b>" から各 <b>...</b> 内の文字列のみを取り出せ(正規表現の非貪欲マッチを使う)。

(解答は上の対応する例を見れば実行できます)


まとめ(いつ使う?)

  • データの正規化・比較(①②③) ⇒ ユーザー入力を扱うアプリで必須。
  • バイトと文字列の切替(④) ⇒ ファイルやネットワークI/O。
  • セキュリティ(⑩) ⇒ Web/DBに出すときは必ず。
  • パフォーマンス(⑥⑬) ⇒ 大量データ処理で差が出る。
  • 正規表現(⑧⑫) ⇒ 複雑な文字列抽出・置換。
  • CSV/JSON(⑪⑭) ⇒ データ入出力での正攻法。
Python
スポンサーリンク
シェアする
@lifehackerをフォローする
スポンサーリンク
タイトルとURLをコピーしました