大量の文字種を同時に置き換えたいとき、str.translate
は強力かつ高速に動作します。
この記事では、初心者の方がつまずきやすい点を避けながら、str.translate
とstr.maketrans
の基本から、str.replace
との違い、実践的な使い分け、そしてベストプラクティスまでを段階的に解説します。
str.translateで複数文字を一括置換する基本
一括置換のメリットと用途
複数の文字をまとめて置換する目的は、処理の正確さと速度を両立することにあります。
replace
を何度も呼び分けると、置換結果が次の置換に影響する(連鎖)ため意図しない結果を招くことがあります。
translate
は変換表に基づいて各文字を独立に(同時に)置換するため、意図どおりの一括変換が可能です。
例えば以下の用途に適しています。
- 複数の記号を一括で削除する
- 特定の文字群(母音、記号、全角の一部など)を別の文字に正規化する
- 簡易的な符号化(例: leetspeak のような文字の置換)
translateと変換表の仕組み
str.translate
は「変換表(マッピング)」を受け取り、各文字のUnicodeコードポイントに対して、対応する置換文字列または削除(None)を適用します。
変換表は次のようなルールで解釈されます。
- 変換表のキーは文字(長さ1の文字)またはその
ord
値(int)です。 - 値は置換後の文字列(長さ1でも複数文字でも可)または
None
です。None
は「削除」を意味します。 - 変換表に存在しない文字はそのまま残ります。
まずは最小限の例を見てみます。
# 基本例: a,b,c を x,y,z に置換し、! と ? を削除する
text = "abc!? cab"
table = {
ord('a'): 'x',
ord('b'): 'y',
ord('c'): 'z',
ord('!'): None,
ord('?'): None,
}
print(text.translate(table)) # 'ord'を使わず文字キーでもOK
xyz zxy
このように、1回のtranslate
で「複数の置換」と「複数の削除」を同時に実行できます。
置換と削除を同時に行う考え方
translate
の考え方はシンプルで、各文字に対して「最終的にどうするか」を辞書で宣言するだけです。
削除ならNone
、置換なら置換先文字列を値にします。
# 置換( - と _ を空白に)と、削除( ! と ? )を同時に適用
text = "A-B_C!? 123"
table = str.maketrans({
'-': ' ', # 置換
'_': ' ', # 置換
'!': None, # 削除
'?': None, # 削除
})
print(text.translate(table))
A B C 123
(この例では連続した空白が残る点にも注意が必要です。必要に応じて後段で空白の正規化を行います。)
str.maketransの作り方と使い方
dictで複数文字をまとめて対応付け
str.maketrans
は、translate
に渡すマッピングを簡単に作るためのユーティリティです。
辞書で指定すると、1対多(文字→複数文字)の置換や、削除の指定がしやすくなります。
# dictでマッピングを作成(1対多の置換や削除も可能)
text = "straße & œuvre!"
table = str.maketrans({
'ß': 'ss', # 1文字→2文字の置換
'œ': 'oe', # 1文字→2文字の置換
'&': 'and', # 1文字→3文字の置換
'!': None, # 削除
})
print(text.translate(table))
strasse and oeuvre
このように辞書を使えば、柔軟な置換が可能です。
2つの文字列から1対1の対応を作る
固定長の1対1置換なら、2つの同じ長さの文字列を渡して手短に変換表を作れます。
これは「各文字を別の1文字へ」置換する用途に向いています。
# 'abc' → 'XYZ' の1対1マッピング
text = "abracadabra"
table = str.maketrans('abc', 'XYZ') # 文字列の長さは同じである必要がある
print(text.translate(table))
XYrXZXdXYrX
注意点として、この方法は1対多(例: ‘ß’→’ss’)の置換はできません。
1対多が必要なら前節の辞書形式を使います。
削除したい文字をまとめて指定
3番目の引数に「削除したい文字すべてを列挙した文字列」を渡すと、簡潔に削除マッピングを作れます。
# 数字と句読点(, !)を一括削除
text = "Hello, world! 123"
table = str.maketrans('', '', '0123456789,!') # 第3引数が削除対象
print(text.translate(table))
Hello world
この例では最後に空白が残っています。
空白の整理が必要なら、後処理でsplit
→join
等を検討します。
translateで変換表を適用
作成した変換表は、文字列のtranslate
メソッドで適用します。
複数のテキストに再利用できるため、同じ正規化処理を何度も実行するワークフローに向いています。
# よく使う正規化: 長音・ダッシュ類や波ダッシュ類を統一、特定記号を削除
texts = [
"price–list — 1,200円",
"range ~ 10〜20",
"dash ― emphasis",
]
# 異体のダッシュや波ダッシュを統一し、カンマは削除
table = str.maketrans({
'–': '-', '—': '-', '―': '-', # ダッシュ系をハイフンに統一
'~': '~', '〜': '~', # 波ダッシュ系をASCIIに統一
',': None, # カンマ削除
})
for s in texts:
print(s.translate(table))
price-list - 1200円
range ~ 10~20
dash - emphasis
必要な範囲に限定して正規化を行うと、副作用を抑えやすくなります。
str.replaceとの違いと使い分け
replaceは単一パターンを逐次置換
str.replace(old, new)
は「1つのパターン」を別の文字列に置換します。
複数の置換を連続で行うと、先の置換結果が次に影響するため、意図しない変換をしてしまうことがあります。
# 連鎖するreplaceの例(結果が次の置換に影響)
s = "abc"
result = s.replace('a', 'b').replace('b', 'c').replace('c', 'a')
print(result)
aaa
本来「a→b、b→c、c→a」を同時に適用したい場合、replace
の連鎖は適していません。
translateは複数文字を同時に一括置換
translate
はマッピングに基づいて各文字へ同時適用するため、上記のような相互変換も意図通りに動きます。
# translateなら同時適用される
s = "abc"
table = str.maketrans({'a': 'b', 'b': 'c', 'c': 'a'})
print(s.translate(table))
bca
この違いが、translate
を使う大きな理由のひとつです。
パフォーマンスとメモリの違い
translate
は内部で1パス処理しながらマッピングを参照するため、多数の1文字置換・削除をまとめて行う場合に高速です。
一方、replace
を複数回呼ぶと、そのたびに新しい文字列が生成されるため、メモリ割り当てとコピーが繰り返され、相対的に遅くなりやすいです。
簡単なベンチマーク例を示します。
# 簡易ベンチマーク(実行環境や条件で結果は変わります)
import timeit
text = ("The quick brown fox jumps over the lazy dog!? 12345\n" * 5000)
# translate: 母音を大文字化、!?を削除、数字を削除
table = str.maketrans({
'a': 'A', 'e': 'E', 'i': 'I', 'o': 'O', 'u': 'U',
'!': None, '?': None,
'0': None, '1': None, '2': None, '3': None, '4': None,
'5': None, '6': None, '7': None, '8': None, '9': None,
})
def via_translate():
return text.translate(table)
def via_replace():
s = text
for old, new in [('a','A'),('e','E'),('i','I'),('o','O'),('u','U')]:
s = s.replace(old, new)
for ch in '!?0123456789':
s = s.replace(ch, '')
return s
print("translate:", timeit.timeit(via_translate, number=10))
print("replace chain:", timeit.timeit(via_replace, number=10))
translate: 0.025
replace chain: 0.091
多くの小さな置換・削除をまとめたいとき、translate
は実務で体感差が出やすいです。
以下は機能面の比較表です。
観点 | str.replace | str.translate |
---|---|---|
パターン単位 | 文字列(1文字以上) | 基本は1文字単位 |
同時適用 | できない(逐次) | できる(同時) |
複数文字の削除 | 1回につき1パターン | 複数文字を一括で削除可能(None/第3引数) |
1→多の置換 | 可能 | dictマッピングなら可能(文字→複数文字) |
文字以外のパターン | 可能(部分文字列) | 不可(基本は1文字) |
パフォーマンス | 連鎖すると遅くなりやすい | 多数の一括置換・削除に強い |
使い分けの判断基準
- 1つの固定フレーズを置換するだけなら
replace
が簡潔です。 - 複数の1文字置換・削除をまとめて行う、または「同時適用」が必要な場合は
translate
が安全かつ高速です。 - 1文字を複数文字に展開したい(例: ‘ß’→’ss’)なら、
translate
に辞書マッピングを渡します。 - 1文字以上の部分文字列を複雑に置換する必要があるなら、
replace
や正規表現(re.sub
)を検討します。
よくある落とし穴とベストプラクティス
1対多の置換はdictを使う
str.maketrans('abc','xyz')
のような2文字列指定は1対1のみです。
1対多(1文字→複数文字)は辞書で定義します。
# 1対多置換の例
text = "maß 5㎏" # '㎏'(U+338F)は互換文字
table = str.maketrans({
'ß': 'ss',
'㎏': 'kg', # 互換用の単位文字をASCIIに
})
print(text.translate(table))
mass 5kg
変換表にない文字はそのまま残る
マッピングに存在しない文字は変更されません。
これは「影響範囲を限定した正規化」がしやすい、という利点にもなります。
text = "abcde"
table = str.maketrans({'a': 'A', 'e': 'E'})
print(text.translate(table)) # b, c, d はそのまま
AbcdE
大文字小文字や全角半角の注意点
translate
は指定した文字にだけ作用します。
大文字小文字を両方扱うなら両方のキーを用意します。
また全角半角の正規化を網羅的に行いたい場合、unicodedata.normalize('NFKC', s)
の活用も検討してください。
translate
と組み合わせると強力です。
# 大文字小文字を揃えつつ特定記号を削除
import unicodedata
text = "AbC!? abc"
# まず互換正規化で全角→半角へ
normalized = unicodedata.normalize('NFKC', text)
# 次に母音を大文字化し、!?を削除
table = str.maketrans({'a':'A','e':'E','i':'I','o':'O','u':'U','!':None,'?':None})
print(normalized.translate(table))
AbC AbC
必要に応じて、正規化の有無や順序を調整します。
連鎖replaceで結果が変わる問題
相互変換や衝突する置換規則があると、replace
の連鎖は結果が崩れます。
translate
は同時適用なので、こうした問題を避けられます。
# 'a'↔'b' の入れ替えで比較
s = "abba"
rep = s.replace('a', 'b').replace('b', 'a') # 逐次: 先の結果に影響
trans = s.translate(str.maketrans({'a':'b','b':'a'})) # 同時
print(rep)
print(trans)
aaaa
baab
変換表の再利用で処理を高速化
同じ変換を繰り返し適用する場合は、変換表を一度だけ作って再利用します。
ループ内で毎回str.maketrans
を作ると無駄が増えます。
# モジュールトップや初期化時に一度だけ用意
NORMALIZE_TABLE = str.maketrans({
'–': '-', '—': '-', '―': '-',
'~': '~', '〜': '~',
',': None, '!': None, '?': None,
})
def normalize_line(s: str) -> str:
# 何度呼んでもテーブルは再利用される
return s.translate(NORMALIZE_TABLE)
# 例: 多数行の前処理
lines = ["price–list—2024,!?","range 10〜20","ok"]
print([normalize_line(x) for x in lines])
['price-list-2024', 'range 10~20', 'ok']
大規模データの前処理で効果が大きく、速度とメモリ効率の改善が見込めます。
まとめ
str.translate
は「複数の1文字置換・削除を同時に行う」用途に理想的で、正確さと速度の両面で有利です。
変換表はstr.maketrans
で簡潔に作成でき、辞書を使えば1対多の置換や多数の削除をまとめて表現できます。
一方、str.replace
は部分文字列を対象とした単発の置換に向いており、複雑な同時変換には不向きです。
実務では、置換の性質(同時か逐次か、1文字か部分文字列か)とパフォーマンス要件を見極め、以下の方針で使い分けるのがおすすめです。
- 多数の1文字置換・削除、相互変換や同時適用が必要なら
translate
を使う - 1つの固定パターンの置換なら
replace
で簡潔に - 1対多の置換は
translate
に辞書マッピングを渡す - 全角半角や互換文字の正規化が必要なら、
unicodedata.normalize
と組み合わせる - 変換表は一度だけ作って再利用し、処理を高速化する
これらを押さえることで、安全で読みやすく、高速な文字列処理が実現できます。