Python 逆引き集 | 文字列操作:format()で文字列を整形する

Python Python
スポンサーリンク
スポンサーリンク

Pythonでformat()を使って文字列を整形する方法

変数の値を文字列に埋め込みたい、数値を指定した桁数で表示したい、テンプレートに後から値を流し込みたい――文字列の整形はプログラミングの現場で頻繁に必要になる処理です。前回の記事で紹介したf文字列が現代の主流ですが、str.format()メソッドはそれ以前から存在する正式な書式化手段であり、今もなお「テンプレート化」「遅延評価」「ログ出力」などの場面で活躍しています。また、既存コードの保守や、f文字列が使えないPython 3.5以前の環境に触れる機会もあるため、format()の読み書きができることは実務上必須のスキルです。この記事では、format()の基本構文から応用、f文字列との使い分け、そしてセキュリティ上の重要な注意点まで、プログラミング初心者の方でも確実に使いこなせるように順を追って解説します。

はじめに:format()の位置づけ

Pythonの文字列整形には、主に3つの世代の方法があります。

  • %書式:C言語由来の古い記法("%s" % name)。現在は非推奨寄り
  • format()メソッド:Python 2.6/3.0で導入された汎用的な書式化メソッド
  • f文字列:Python 3.6で導入された、最も読みやすい現代的な記法

現時点では新規コードではf文字列が推奨されますが、format()にはf文字列にはない強みもあります。代表的なものは以下の通りです。

  • テンプレート化できる:整形の「型」を文字列として保持し、後から値を流し込める
  • 遅延評価ができる:ログ出力など、実際に使われるまで評価を遅らせられる
  • 動的な書式指定:ユーザー指定の幅や桁数に対応できる

まずは基本構文から見ていきましょう。

基本編:format()の基本的な使い方

基本構文:{}に値を流し込む

format()の基本は、文字列の中にプレースホルダ(埋め込み場所)として{}を置き、.format(値)で値を流し込む形です。プレースホルダの数と渡す値の数が一致していれば、順番に埋め込まれます。

# format()の基本例
name = "太郎"
age = 25

# {}の部分にformat()の引数が順番に入る
message = "名前: {}, 年齢: {}".format(name, age)
print(message)  # 名前: 太郎, 年齢: 25

# 値は直接渡すこともできる
print("合計: {}円".format(1200 + 800))  # 合計: 2000円
Python

f文字列との違いは明確です。format()は「テンプレート文字列」と「値」を分離して書くため、テンプレートを変数に保持しておき、あとから複数回使い回すことができます。

# テンプレートを変数に保持して使い回す例(format()ならではの使い方)
template = "こんにちは、{}さん。本日は{}回目のご来店です。"

print(template.format("太郎", 1))  # こんにちは、太郎さん。本日は1回目のご来店です。
print(template.format("花子", 5))  # こんにちは、花子さん。本日は5回目のご来店です。
Python

インデックス指定:{0}、{1}で順番を制御する

プレースホルダに{0}や{1}のようにインデックスを付けると、同じ値を複数回使ったり、順番を入れ替えたりできます。

# インデックス指定で柔軟に埋め込む例
name = "太郎"

# {0}は1番目の引数、{1}は2番目の引数を指す
print("{0}さん、{0}さん、こちらです".format(name))  # 太郎さん、太郎さん、こちらです

# 引数の順番と異なる順番で埋め込める
print("{1}年{0}月{2}日".format(10, 2026, 9))  # 2026年10月9日
Python

キーワード指定:{name}のように名前で指定する

プレースホルダに名前を付けて、.format(名前=値)の形で渡す方法もあります。コードの意図が伝わりやすくなるため、引数が多い場合や、可読性を重視したい場合に有効です。

# キーワード指定の例
message = "氏名: {name}、年齢: {age}歳、出身: {city}".format(
    name="次郎", age=30, city="大阪"
)
print(message)  # 氏名: 次郎、年齢: 30歳、出身: 大阪
Python

辞書の**展開(アンパック)と組み合わせると、キーワード指定がさらに便利になります。

# 辞書を**で展開して一括で渡す例
user = {"name": "花子", "age": 28, "city": "東京"}

# **userで辞書のキーと値が展開され、名前付き引数として渡される
print("氏名: {name}、{age}歳、{city}在住".format(**user))
# 氏名: 花子、28歳、東京在住
Python

発展編:数値の整形とアクセス記法

数値の書式指定(:の後ろに指定子を書く)

プレースホルダの中に:を入れて書式指定を書くと、数値の表示形式を細かく制御できます。主な指定子は以下の通りです。

  • {:.2f}:小数点以下2桁に丸める
  • {:,}:3桁ごとにカンマ(1,000,000)
  • {:>10}:右寄せで10文字分の幅
  • {:<10}:左寄せで10文字分の幅
  • {:^10}:中央揃えで10文字分の幅
  • {:05d}:0埋めで5桁(00042)
  • {:.1%}:パーセント表示で小数1桁(85.6%)
# 数値の書式指定例
price = 1234567.891

print("{:.2f}".format(price))     # 1234567.89(小数2桁)
print("{:,.2f}".format(price))    # 1,234,567.89(カンマ区切り)
print("{:10.2f}".format(price))   # ' 1234567.89'(右寄せ10文字)
print("{:+,.2f}".format(price))   # +1,234,567.89(符号付き)

# 0埋め(ファイル連番など)
print("file_{:03d}.csv".format(7))  # file_007.csv

# パーセント
print("達成率: {:.1%}".format(0.856))  # 達成率: 85.6%

# 日時
from datetime import datetime
now = datetime(2026, 10, 9)
print("{:%Y年%m月%d日}".format(now))  # 2026年10月09日
Python

重要なのは、この書式指定のルールはf文字列でもまったく同じである点です。f"{price:,.2f}"と"{:, .2f}".format(price)(スペースなし)の書式部分は共通なので、書式指定を覚えれば両方の記法に応用が効きます。

辞書・リストの要素を直接参照する

format()のプレースホルダは、{0[key]}や{0[0]}のように、渡したオブジェクトの要素に直接アクセスする記法を持っています。これはf文字列にはないformat()特有の機能です。

# 辞書の要素をプレースホルダ内で参照する例
user = {"name": "太郎", "scores": [80, 92, 75]}

# {0[name]}:0番目の引数(辞書)の"name"キー
# {0[scores][1]}:辞書の"scores"リストの1番目の要素
print("{0[name]}さんの2回目の点数: {0[scores][1]}点".format(user))
# 太郎さんの2回目の点数: 92点
Python

ただし、この記法は後述するセキュリティ上のリスク(属性アクセスの悪用)にも関わるため、外部入力を伴う場面では慎重な設計が必要です。

実務編:format()が活きる場面とテンプレート

場面1:テンプレート文字列の保持と再利用

format()の最大の強みは、整形の「型」を文字列として保持できる点です。設定ファイルや定数としてテンプレートを定義し、実行時に値を流し込む設計は、実務で広く使われます。

# 実務テンプレート:メッセージ定数の集中管理
class Messages:
    LOGIN_SUCCESS = "{username}さん、ログインしました({time})"
    LOGIN_FAILED  = "ログイン失敗: ユーザー「{username}」"
    FILE_SAVED    = "ファイルを保存しました: {filepath}({size_kb:.1f}KB)"

import datetime

username = "taro"
print(Messages.LOGIN_SUCCESS.format(
    username=username,
    time=datetime.datetime.now().strftime("%H:%M:%S")
))
# taroさん、ログインしました(18:30:00)
Python

このように「文字列の型」と「データ」を分離することで、メッセージの一括管理、多言語対応(i18n)、テストのしやすさといった実務上のメリットが得られます。

場面2:ログ出力での遅延評価

loggingモジュールでは、ログレベルによっては実際に出力されないメッセージが存在します。format()を使わずに文字列を事前に作ると、その分のコストが無駄になります。loggingでは内部的に%書式を使いますが、format()の考え方と同じく「テンプレートと値を分離して遅延評価する」パターンが活きています。

# ログ出力の遅延評価の例
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

user_id = 12345

# %sの部分は実際にログが出力されるタイミングで評価される
# (DEBUGレベルなら出力されず、文字列の生成コストもかからない)
logger.debug("ユーザー詳細: %s", user_id)  # INFOレベルでは出力されない
logger.info("ログイン: ユーザーID %s", user_id)  # ログイン: ユーザーID 12345
Python

f文字列は評価が即時に行われるため、「高頻度だが出力されないログ」では無駄なコストが発生します。パフォーマンスが問われるログ出力では、テンプレート分離の考え方を持つことが重要です。

場面3:動的な書式指定への対応

「表示桁数をユーザーが設定できる」ようなケースでは、書式指定自体を変数にできないf文字列よりも、format()の方が向いています。

# 動的に書式を変える例
def format_number(value, decimals):
    # 書式指定部分を動的に組み立てる
    fmt = "{:." + str(decimals) + "f}"
    return fmt.format(value)

print(format_number(3.14159, 2))  # 3.14
print(format_number(3.14159, 4))  # 3.1416

# より安全な方法:書式指定文字列全体を検証してから使う
def format_number_safe(value, decimals):
    if not isinstance(decimals, int) or decimals < 0 or decimals > 10:
        raise ValueError("桁数は0〜10の整数で指定してください")
    return "{:.{d}f}".format(value, d=decimals)  # ネスト指定も可能

print(format_number_safe(3.14159, 3))  # 3.142
Python

注意すべき重要なポイント

中括弧{}のエスケープ:{{}}とする

format()の文字列内で中括弧そのものを表示したい場合は、{{と}}のように2重に書きます。これを忘れると、プレースホルダと解釈されてエラーか誤動作の原因になります。

# 中括弧をリテラルで表示する例
name = "太郎"

# {}を表示したい場合は{{}}と書く
print("{{name}}は{}です".format(name))  # {name}は太郎です

# JSONっぽい文字列を作る例
data = {"status": "ok"}
import json
print("結果: {}".format(json.dumps(data, ensure_ascii=False)))
# 結果: {"status": "ok"}(dumpsが返す文字列内の括弧はそのまま表示される)
Python

プレースホルダと引数の不一致はエラーになる

プレースホルダの数と引数の数が一致しないと、IndexErrorやKeyErrorが発生します。動的にテンプレートを扱う場合は、テストで全パターンをカバーするか、事前に検証するロジックを入れると安全です。

# 【エラー例】プレースホルダと引数の不一致
# "{} {}".format("a")          # IndexError: Replacement index 1 out of range
# "{name}".format(name="a")    # OK
# "{unknown}".format(name="a") # KeyError: 'unknown'

# 安全に呼び出すためのテンプレート
def safe_format(template, **kwargs):
    try:
        return template.format(**kwargs)
    except (KeyError, IndexError) as e:
        return f"[フォーマットエラー: {e}]"

print(safe_format("{name}さん", name="太郎"))     # 太郎さん
print(safe_format("{unknown}さん", name="太郎"))  # [フォーマットエラー: 'unknown']
Python

セキュリティ上の重要な警告:format文字列インジェクション

これは本記事で最も重要なセキュリティの注意点です。ユーザーから入力された文字列をformat()の「テンプレート側」に使ってはいけません。なぜなら、format()のプレースホルダには{0.__class__}や{0.__init__.__globals__}のようなオブジェクトの内部属性にアクセスする記法があり、悪意のある入力によって内部情報の漏洩やコード実行の足がかりを与える可能性があるためです。

# 【危険な例】ユーザー入力をformatのテンプレートに使う
user_input = "{0.__class__}"  # 攻撃者が仕掛けた可能性のある入力

class Secret:
    pass

# ユーザー入力をテンプレート側にすると、オブジェクトの内部が覗ける
print(user_input.format(Secret()))
# <class '__main__.Secret'>(本来見せたくない内部情報が漏洩する)

# さらに悪質な例(概念)
# "{0.__init__.__globals__[SECRET_KEY]}".format(obj) のような
# アクセスでアプリケーションの秘密情報を盗まれる可能性がある
Python

つまり、「テンプレートは必ず開発者が用意した固定文字列に限定し、ユーザー入力は値(引数)側にだけ流し込む」という設計原則が絶対です。外部からの文字列をテンプレートに組み込む必要がある場合は、string.Template(プレースホルダの機能が限定され、属性アクセスができない安全なテンプレートクラス)の利用が検討されます。

# 安全な文字列テンプレートの例(string.Template)
from string import Template

# $name のような単純なプレースホルダのみ(属性アクセス記法は使えない)
template = Template("こんにちは、$nameさん")
print(template.substitute(name="太郎"))  # こんにちは、太郎さん

# ユーザー入力をテンプレートにする場合でも、
# Templateでは {0.__class__} のような記法は機能しないため安全側に倒せる
user_template = Template("$who が来ました")
print(user_template.substitute(who="客人"))  # 客人 が来ました
Python

f文字列との使い分けを意識する

「どちらを使うべきか迷ったら」、以下の基準で判断するとよいでしょう。

  • f文字列を選ぶ場面:変数をその場で埋め込む、シンプルな整形、新規コード
  • format()を選ぶ場面:テンプレートを変数として保持・再利用する、動的な書式指定が必要、ログの遅延評価、既存コードの保守
# 使い分けの例:テンプレートの保持ならformat()、その場の埋め込みならf文字列

# テンプレートはformat()で定義(再利用のため)
ROW_TEMPLATE = "| {id:4d} | {name:10s} | {score:5.1f} |"

rows = [
    {"id": 1, "name": "太郎", "score": 85.5},
    {"id": 2, "name": "花子", "score": 92.0},
]

for row in rows:
    print(ROW_TEMPLATE.format(**row))
# |    1 | 太郎       |  85.5 |
# |    2 | 花子       |  92.0 |

# その場で1回だけ使うならf文字列の方が読みやすい
for row in rows:
    print(f"| {row['id']:4d} | {row['name']:10s} | {row['score']:5.1f} |")
# (結果は同じ)
Python

Noneや予期しない型への対策

format()にNoneや予期しない型を渡すと、書式指定に応じてTypeErrorが発生することがあります。外部データを流し込む場合は、型チェックやデフォルト値の設定を行ってください。

# None対策の例
def format_score(score):
    # Noneや非数値の場合はデフォルト表示にする
    if score is None:
        return "未採点"
    try:
        return "{:.1f}点".format(float(score))
    except (TypeError, ValueError):
        return "未採点"

print(format_score(85.567))  # 85.6点
print(format_score(None))    # 未採点
print(format_score("abc"))   # 未採点
Python

まとめ

format()は、{}プレースホルダに値を流し込んで文字列を整形する、Pythonの正式な書式化メソッドです。位置指定({0})、キーワード指定({name})、書式指定({:.2f})など、豊富な記法を持ち、テンプレートの保持と再利用というf文字列にはない強みを発揮します。書式指定のルールはf文字列と共通であるため、両方の記法を使いこなせるようになると、実務の幅が大きく広がります。

最も肝心なのはセキュリティ上の原則です。テンプレート側には開発者が用意した固定文字列のみを使い、ユーザー入力は値(引数)側にだけ渡すこと。ユーザー入力をテンプレートにしてしまうと、オブジェクトの内部属性にアクセスされ、情報漏洩やコード実行の危険にさらされます。この原則を守り、必要に応じてstring.Templateを使い分ければ、format()は安全かつ強力な武器になります。まずは手元のコードでf文字列とformat()を比較しながら、それぞれの得意な場面を体感してみてください。

タイトルとURLをコピーしました