Pythonで文字列を結合したり、同じ文字列を繰り返したりする方法はいくつもあります。
+演算子、str.join()、f文字列、*演算子はそれぞれ得意分野が少しずつ異なります。
この記事では、それぞれの使い方と使い分けのコツ、注意点や実用的な例、パフォーマンスまで丁寧に解説します。
Pythonの文字列結合・繰り返しの基本
どの方法をいつ使うか:+、join、f文字列、*
状況に応じて適切な手法を選ぶと、コードが読みやすく、速く、バグも減ります。
以下の表はおおまかな指針です。
手法 | 主な用途 | 書きやすさ | パフォーマンス | 例 |
---|---|---|---|---|
+ 演算子 | 少数の文字列を手早く結合 | とても簡単 | 小規模なら十分、ループでの多用は非推奨 | "Hello, " + name |
str.join() | 多数要素を区切り付きで連結 | やや慣れが必要 | 大量データに最適 | ", ".join(items) |
f文字列 | 変数や式を読みやすく埋め込み | 高 | 小〜中規模で良好 | f"{user} has {n:,} pts" |
* 演算子 | 同じ文字列の繰り返し | 高 | 高 | "-" * 80 |
区切り文字・改行・空白の扱いの基本
結合の成否は「どの区切りを入れるか」に左右されます。
スペースや改行の入れ忘れ・入れ過ぎはバグや見た目の崩れに直結します。
区切りが明確な場合はstr.join()
を使うと意図を表現しやすく、改行は"\n"
を使って明示的に制御します。
print()
を使う場合はsep
とend
引数で区切りや末尾の改行を調整できます。
また、余分な空白はstrip()
やrstrip()
で取り除くと安定します。
# 改行や空白の扱い
names = ["Alice", "Bob", "Carol"]
print(", ".join(names)) # 区切り: カンマ+スペース
print("\n".join(names)) # 区切り: 改行
print("A", "B", "C", sep="\t") # printの区切りをタブに
print("line without newline", end="") # 末尾改行なし
Alice, Bob, Carol
Alice
Bob
Carol
A B C
line without newline
+演算子で文字列を結合する
+でシンプルに連結する使い方
+演算子は最も直感的で、小規模な結合に適しています。
スペースや句読点は文字列として明示的に入れます。
# + で簡単に連結
first = "Python"
second = "3.12"
message = "Using " + first + " " + second + "!"
print(message)
Using Python 3.12!
ヒント
インラインで小さな数の文字列を合わせるなら+で十分読みやすく、過剰な抽象化を避けられます。
複数回の+連結が遅くなるケースと対策
+でループしながら追記すると、都度新しい文字列が作られメモリコピーが発生します。
要素数が増えると二乗オーダーに近いコストになりやすく、パフォーマンス低下の原因になります。
対策は「リストに溜めて最後にjoin()
」です。
# 悪い例: + をループで使ってどんどん足す
def concat_plus(n: int) -> str:
s = ""
for i in range(n):
s += str(i) # 毎回新しい文字列を生成
return s
# 良い例: 一旦リストに集めて join
def concat_join(n: int) -> str:
parts = []
for i in range(n):
parts.append(str(i))
return "".join(parts)
print(len(concat_plus(10_000)))
print(len(concat_join(10_000)))
28940
28940
上記は機能的には同じ結果ですが、大きなn
で実行時間が大きく違います。
str.join()でリストやイテラブルを結合する
joinの基本構文と区切り文字の指定
"区切り".join(イテラブル)
の形で使います。
区切りが明確な時に最もわかりやすく効率的です。
# joinの基本
items = ["spam", "eggs", "ham"]
csv = ",".join(items)
lines = "\n".join(items)
print(csv)
print("---")
print(lines)
spam,eggs,ham
---
spam
eggs
ham
非文字列(数値・None)を結合する:map(str)の活用
join()
は要素がすべて文字列である必要があります。
数値やNone
が混ざる場合は、map(str, iterable)
や内包表記で文字列化、不要要素の除外を行います。
# 数値や None を含む場合の対処
data = [1, 2, None, 4, 5]
# 1) すべてを文字列化(None -> "None" になる)
s1 = ", ".join(map(str, data))
# 2) None を除外して結合
s2 = ", ".join(str(x) for x in data if x is not None)
print(s1)
print(s2)
1, 2, None, 4, 5
1, 2, 4, 5
大量データの結合にjoinが向く理由
join()
は結合結果の総バイト数を先に見積もって、必要なメモリを一度だけ確保してからコピーします。
これにより、ループ中に断続的な再確保とコピーが発生する+
連結に比べて格段に効率的になります。
ファイル出力や大きなログ文字列の組み立てなど、要素数が多い場面ではjoin()
が基本になります。
f文字列で読みやすく結合する
f文字列の基本と式展開・フォーマット指定子
f文字列はリテラル内に{式}
を書いて、その評価結果を直接埋め込めます。
桁区切りや小数点以下の桁数などのフォーマット指定も簡潔です(Python 3.6+)。
# f文字列の基本
user = "Alice"
points = 1234567
ratio = 0.0789
msg = f"{user} has {points:,} points (ratio={ratio:.2%})"
print(msg)
Alice has 1,234,567 points (ratio=7.89%)
補足
:,
は3桁区切り:.2%
はパーセント表示(小数2桁)
f文字列とformat()の違い・使い分け
str.format()
でも同様の書式化が可能ですが、f文字列の方が短く読みやすいコードになりやすいです。
一方でフォーマットテンプレートを事前に用意して複数回使い回す場合や翻訳文字列に適用する場合は、format()
やTemplate
の方が便利なこともあります。
# 同等の処理を format() で
template = "{user} has {points:,} points (ratio={ratio:.2%})"
msg = template.format(user=user, points=points, ratio=ratio)
print(msg)
Alice has 1,234,567 points (ratio=7.89%)
- 単発の表示やログ行の構築: f文字列が読みやすい
- テンプレートの再利用や国際化:
format()
やstring.Template
の利用を検討
*演算子で文字列を繰り返す
*で文字列をn回繰り返す基本
文字列 * n
で繰り返しができます。
罫線やインデント、単純なパディング生成に向きます。
# 基本的な繰り返し
print("-" * 10)
print("ab" * 3)
----------
ababab
パディングや罫線の生成など実用例
繰り返しは、ヘッダーの下線や簡易テーブルの横罫線、インデントの生成など日常的に役立ちます。
# 罫線やインデントの例
title = "Report"
line = "=" * len(title)
print(title)
print(line)
indent = " " * 4 # スペース4つのインデント
print(indent + "• item 1")
print(indent + "• item 2")
Report
======
• item 1
• item 2
より厳密なパディングはljust/rjust/center
やzfill
も便利です。
# 幅合わせ(桁埋め)
print("42".zfill(5)) # 0埋め
print("cat".rjust(6, ".")) # 右寄せ・ドット埋め
print("dog".center(7, "-")) # 中央寄せ・ハイフン埋め
00042
...cat
--dog--
使い分け・ベストプラクティスと注意点
パフォーマンス比較:+ vs join vs f文字列 vs *
あくまで一例ですが、以下のようにtimeit
で比較できます(実行環境で数値は変わります)。
傾向として「多数要素の連結はjoin」「繰り返しは*」が速いです。
# 簡易ベンチマーク(値は環境で変わります)
import timeit
setup = """
items = [str(i) for i in range(5000)]
"""
t_plus = timeit.timeit(
stmt='"".join([s for s in items])' # 参考として join の速さを見る
, setup=setup, number=50)
t_bad_plus = timeit.timeit(
stmt='''
s = ""
for x in items:
s += x
''', setup=setup, number=10)
t_join = timeit.timeit(
stmt='",".join(items)', setup=setup, number=1000)
t_f = timeit.timeit(
stmt='f"{items[0]}-{items[1]}-{items[2]}"', setup=setup, number=500000)
t_mul = timeit.timeit(
stmt='"-" * 1000', number=500000)
print("join (good pattern):", t_join)
print("+ in loop (bad pattern):", t_bad_plus)
print("f-string (small pieces):", t_f)
print("* repeat:", t_mul)
join (good pattern): 0.12
+ in loop (bad pattern): 1.85
f-string (small pieces): 0.04
* repeat: 0.02
目安として、要素数が多い結合はjoin()
、小規模な文字列合成はf文字列や+、繰り返しは*
を選ぶと良いです。
改行・タブ・空文字の扱いとstrip/joinの併用
行末や先頭の余分な空白を取り除いてから結合すると、レイアウトが安定します。
タブや複数スペースを統一する際も、分割→整形→結合が有効です。
# 前後の空白除去と改行での結合
raw_lines = [" apple ", "", " banana", "cherry "]
clean = [s.strip() for s in raw_lines if s.strip() != ""]
text = "\n".join(clean)
print(text)
apple
banana
cherry
print()
で区切りや末尾を制御する方法も安定運用に役立ちます。
# printのsep/end
print("A", "B", "C", sep=" | ", end="\n\n") # 区切りを明示、2重改行
print("done", end="") # 最後に改行しない
A | B | C
done
型変換とエラー対策:str()、型アノテーション、Noneチェック
数値やNone
が混ざるとTypeError
が出ます。
関数インターフェースの段階で型アノテーションを付け、早めにNone
チェックやstr()
での明示変換を行うと安全です。
from typing import Iterable, Optional
def join_safe(sep: str, items: Iterable[Optional[object]]) -> str:
"""
None はスキップし、他は str() によって文字列化して結合する安全な関数。
"""
filtered = (str(x) for x in items if x is not None)
return sep.join(filtered)
print(join_safe(", ", [1, None, "x", 3.14]))
1, x, 3.14
場合によってはNone
を許さず、早期に例外を投げるのが正しいこともあります。
def must_join_strings(sep: str, items: Iterable[str]) -> str:
for x in items:
if not isinstance(x, str):
raise TypeError("all items must be str")
return sep.join(items)
try:
print(must_join_strings(", ", ["ok", 1])) # ここで TypeError
except Exception as e:
print(repr(e))
TypeError('all items must be str')
マルチバイト文字(日本語・絵文字)と繰り返しの注意点
Pythonの文字列はUnicodeであり、「文字数」と「表示幅」は一致しません。
全角文字は等幅フォントでも幅2として表示されることがあり、絵文字や合成シーケンス(例: 国旗 🇯🇵 は2コードポイント)ではさらに複雑です。
*
で繰り返すこと自体は安全ですが、列幅そろえには注意が必要です。
表示幅で整列したい場合はwcwidth
ライブラリの利用が有効です。
pip install wcwidth
# 表示幅に基づくパディング(wcwidthが必要)
# pip install wcwidth
from wcwidth import wcswidth
def pad_display_width(s: str, width: int, fill: str = " ") -> str:
"""
表示幅がwidthになるよう右側をfillで埋める。
"""
w = wcswidth(s)
if w < 0:
# 測定不能な場合はそのまま返す
return s
return s + (fill * max(0, width - w))
items = ["abc", "こんにちは", "🍣", "🇯🇵JP"]
for x in items:
print(pad_display_width(x, 10, ".") + "|")
abc.......|
こんにちは|
🍣........|
🇯🇵JP......|
上記のように、コードポイント数ではなく表示幅に基づく調整で見た目が整います。
コンソールやフォントによって幅の扱いが異なる点も覚えておくと良いでしょう。
まとめ
- 少数の結合は+やf文字列で簡潔に書けます。読みやすさ重視ならf文字列が便利です。
- 多数要素や区切り付きの結合は
str.join()
が基本で、高速かつメモリ効率に優れます。 - 同じ文字列の繰り返しは
*
が最短・最速です。罫線やインデント、簡単なパディングに活用できます。 - 改行や空白は明示的に扱い、
strip()
やprint(sep=..., end=...)
で制御すると安定します。 - 非文字列を混ぜるときは
str()
やmap(str)
、None
の扱い方針(除外・明示文字列化・例外)を決めておきましょう。 - 日本語・絵文字の整列は表示幅の問題があるため、必要に応じて
wcwidth
などで幅を測ると安心です。
これらの原則とテクニックを押さえておけば、文字列の結合・繰り返しは性能と可読性の双方で安定したコードに仕上がります。