f-stringの書き方を覚えて、文字列に変数を埋め込むのにも慣れてきた。そんなころに会社のコードやネットの記事を読むと、見慣れない書き方に出会うことがあります。
"%sさん" % name や "{}さん".format(name) のような書き方です。どちらも文字列に値を埋め込む方法なのですが、初めて見ると何をしているのか戸惑いますよね。
この記事では、f-stringより前から使われてきた2つの書き方を、読めるようになることを目標に解説します。あわせて、今でもこれらを使うべき場面がある理由にも触れていきます。
サンプルの実行結果は、手元のPython 3.11と3.13で確認したものです。
文字列に値を埋め込む方法は3つある¶
最初に、全体像を整理しておきましょう。Pythonには、文字列の中に変数の値を埋め込む方法が大きく分けて3つあります。
それぞれの書き方と登場した時期を表にまとめました。
| 書き方 | 例 | 特徴 |
|---|---|---|
| %書式 | "%sさん" % name |
Pythonの最初期からある。C言語のprintfが元になっている |
| str.format() | "{}さん".format(name) |
Python 3で登場し、2.7にも取り込まれた |
| f-string | f"{name}さん" |
Python 3.6で登場。今いちばんよく使われる |
新しく書くコードなら、ほとんどの場面でf-stringを選べば問題ありません。f-stringの基本については、こちらの記事で詳しく紹介しています。
【関連記事】Pythonのf-stringとは?デバッグが驚くほど楽になる!
それでも残りの2つを知っておく価値はあります。何年も前に書かれたコードは今も現役で動いていますし、ログ出力のように古い書き方のほうが向いている場面もあるからです。
%書式の基本¶
まずは、いちばん古い%書式から見ていきます。文字列の中に%sのような目印を置き、文字列の後ろに%と値を書くと、目印の位置に値が入ります。
実際に動かしてみましょう。値が2つ以上あるときは、タプルにまとめて渡します。
name = "佐藤"
print("%sさん、こんにちは" % name)
# 佐藤さん、こんにちは
print("%sさんは%d歳です" % ("佐藤", 30))
# 佐藤さんは30歳です
ここで使っている%sや%dは、変換指定子と呼ばれる目印です。sは文字列、dは整数というように、%の後ろの1文字で値をどう表示するかを決めます。
よく使う変換指定子¶
変換指定子はたくさんありますが、日常的に見かけるものは限られています。よく出てくるものを表にまとめました。
| 指定子 | 意味 | 例と結果 |
|---|---|---|
%s |
str()で文字列にする | "%s" % 3.5 → 3.5 |
%r |
repr()で文字列にする | "%r" % "a" → 'a' |
%d |
整数 | "%d" % 3.9 → 3 |
%f |
小数 | "%.2f" % 3.14159 → 3.14 |
%x |
16進数 | "%x" % 255 → ff |
%% |
%記号そのもの | "50%%" % () → 50% |
表の中で気をつけたいのは%dです。小数を渡すとエラーにはならず、小数点以下が切り捨てられて整数になります。四捨五入ではないので、金額などを表示するときは意外な結果に驚くかもしれません。
もうひとつ見落としやすいのが%%です。%書式を使った文字列の中で%記号を表示したいときは、%を2つ重ねて書く必要があります。
幅と桁数を指定する¶
%と変換指定子のあいだに数字を書くと、表示する幅や小数の桁数を決められます。表のように列をそろえて出力したいときに便利です。
print("[%5d]" % 42) # [ 42] 5文字分の幅で右寄せ
print("[%-5d]" % 42) # [42 ] -を付けると左寄せ
print("%05.1f" % 3.14159) # 003.1 0で埋めて全体5文字、小数1桁
%書式の落とし穴:タプルを1つだけ渡すとエラーになる¶
%書式には、初心者がつまずきやすい有名な落とし穴があります。座標のようなタプルを、そのまま1つの値として表示しようとしたときです。
次のコードを見てください。目印は1つしかないのに、エラーになってしまいます。
point = (3, 5)
print("座標: %s" % point)
# TypeError: not all arguments converted during string formatting
なぜこうなるのでしょうか? %の右側にタプルを置くと、Pythonはそれを1つの値ではなく、埋め込む値の一覧だと受け取ります。目印は1つなのに値が2つ届いたため、余った値を変換できずにエラーになるのです。
直し方は、タプルをもう一段タプルで包むことです。要素が1つのタプルには、最後にカンマが必要なので注意してください。
print("座標: %s" % (point,))
# 座標: (3, 5)
Python公式ドキュメントでも、%書式はタプルや辞書を正しく表示するときに注意が要ると説明されています。新しいコードにはstr.format()やf-stringをすすめつつ、%書式を廃止する予定はないとも書かれています。
私も10年ほど開発に関わってきた中で、このエラーには何度か出会いました。普段は文字列しか来ない変数に、ある日だけタプルが入ってきて、ログ出力の行で処理が止まったのです。
str.format()の基本¶
次は、Python 3で登場したstr.format()です。文字列の中に{}という目印を置き、formatメソッドに値を渡すと、{}の位置に順番に入ります。
基本の使い方を3つ並べてみました。番号や名前で、どの値をどこに入れるか指定することもできます。
print("{}さんは{}歳です".format("佐藤", 30))
# 佐藤さんは30歳です
print("{0}と{1}と{0}".format("A", "B"))
# AとBとA 同じ値を何度でも使える
print("{name}さんは{age}歳".format(name="佐藤", age=30))
# 佐藤さんは30歳
%書式と違い、何を渡しても1つの値として扱われます。そのため、先ほどのタプルの落とし穴は起きません。
ちなみに、文字列の中で{や}そのものを表示したいときは、{{と}}のように2つ重ねて書きます。%書式で%%と書くのと同じ考え方ですね。
書式指定はf-stringと共通¶
str.format()でうれしいのは、{}の中にコロンを書いて表示の仕方を細かく決められることです。この書式指定は、f-stringでもまったく同じ書き方で使えます。
つまり、ここで覚えたことはそのままf-stringにも活かせます。よく使う書式指定を表にまとめました。
| 書式指定 | 意味 | 例と結果 |
|---|---|---|
{:>8} |
8文字分で右寄せ | "右" → 右 |
{:<8} |
8文字分で左寄せ | "左" → 左 |
{:^8} |
8文字分で中央寄せ | "中" → 中 |
{:*^9} |
*で埋めて中央寄せ | "中" → ****中**** |
{:,} |
3桁ごとにカンマ | 1234567 → 1,234,567 |
{:.2f} |
小数2桁 | 3.14159 → 3.14 |
{:.1%} |
パーセント表示 | 0.256 → 25.6% |
{:08.3f} |
0埋めで全体8文字 | 3.14159 → 0003.142 |
{:b} |
2進数 | 10 → 1010 |
f-stringで書くなら、f"{price:,}円" のようにコロン以下を同じ形で書きます。金額にカンマを付ける処理は実務でもよく使うので、覚えておくと便利です。
format()が今も活躍する場面¶
f-stringがあるなら、format()はもう要らないのでしょうか? 実はそうとも言い切れません。
f-stringは、コードを書いたその場所で値を埋め込みます。一方のformat()は、テンプレートとなる文字列を先に用意しておき、あとから値を入れられるのが強みです。
テンプレートを先に用意しておく¶
たとえば、通知メッセージの文面を設定ファイルや定数にまとめておきたい場面を考えてみましょう。文面は1か所で管理し、送るときに名前や件数を差し込みます。
MESSAGE = "{name}さん、未読のお知らせが{count}件あります"
print(MESSAGE.format(name="佐藤", count=3))
print(MESSAGE.format(name="鈴木", count=12))
f-stringでは、このように文面だけを先に置いておくことはできません。f-stringは書いた瞬間に評価されるので、その時点で変数が決まっている必要があるからです。
ユーザーが書いたテンプレートには使わない¶
ただし、format()には注意点があります。テンプレートそのものをユーザーが書ける仕組みでは、format()を使ってはいけません。
format()の{}の中には、ドットで属性をたどる書き方ができます。そのため、悪意のあるテンプレートを書かれると、見せるつもりのない値まで表示されてしまうのです。
class Config:
def __init__(self):
self.secret_key = "s3cr3t"
class User:
def __init__(self, name):
self.name = name
self.config = Config()
# ユーザーが入力したテンプレートだとする
template = "こんにちは、{user.config.secret_key}さん"
print(template.format(user=User("佐藤")))
# こんにちは、s3cr3tさん 秘密の値が漏れてしまう
こうした場面では、標準ライブラリのstring.Templateを使います。$nameのような目印に値を入れるだけのシンプルな仕組みで、属性をたどることはできません。
from string import Template
t = Template("$name さんの残高は $balance 円")
print(t.safe_substitute(name="佐藤"))
# 佐藤 さんの残高は $balance 円
substitute()は値が足りないとKeyErrorになりますが、safe_substitute()は足りない目印をそのまま残します。外から受け取ったテンプレートを扱うなら、こちらのほうが安全です。
loggingでは今も%sが推奨される¶
古い書き方を知っておくべきいちばんの理由が、ログ出力です。loggingモジュールでは、今でも%書式の形でメッセージを書くのが基本になっています。
次のように、メッセージとは別に値を引数で渡す書き方をします。%演算子は使わず、カンマで区切る点に気をつけてください。
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s")
logger = logging.getLogger(__name__)
user_name = "佐藤"
logger.info("%sさんがログインしました", user_name)
# INFO:佐藤さんがログインしました
logger.debug("検索条件: %s", {"keyword": "python"})
# レベルがINFOなので表示されない
この書き方の利点は、ログが実際に出力されるときにだけ文字列が組み立てられることです。上の例のdebugのように表示されないログでは、文字列を作る処理そのものが省かれます。
f-stringで書くと、ログを出すかどうかに関係なく、その行に来た時点で文字列が作られます。1回あたりはわずかでも、大量のデバッグログを仕込んだループでは無視できない差になることがあります。
リンターもf-stringのログを指摘する¶
この考え方は、コードの書き方をチェックするリンターにも組み込まれています。PylintにはW1203、RuffにはG004というルールがあり、loggingの呼び出しでf-stringを使うと指摘されます。
どちらのルールも、直し方として%sと引数を分けて渡す書き方を示しています。ロギングの基本は、こちらの記事でまとめています。
【関連記事】Pythonのロギング(logging)入門。print卒業!プロが使うログ出力の正しい作法
実務でも、コードレビューでこの点を指摘したことが何度もあります。チームでRuffを導入してからは、レビューで言う前にツールが教えてくれるようになり、やり取りがぐっと減りました。
3つの書き方をどう使い分けるか¶
ここまでの内容を、場面ごとの使い分けとして整理しておきます。迷ったときの目安にしてください。
| 場面 | おすすめ | 理由 |
|---|---|---|
| ふだんの文字列組み立て | f-string | 短く読みやすい |
| 文面を先に用意して後から埋める | str.format() | テンプレートを定数や設定に置ける |
| ユーザーが書いたテンプレート | string.Template | 属性をたどれないので安全 |
| ログ出力 | loggingの%s+引数 | 出力されるときだけ文字列を作る |
| 古いコードを読む | %書式とformat()の知識 | 書き換えなくても意味がわかる |
文字列の扱い全般を見直したい方は、文字列メソッドの記事もあわせて読んでみてください。
【関連記事】Pythonの文字列メソッドとは?strip・split・replaceでデータを整える基本を初心者向けに解説
まとめ¶
最後に、この記事のポイントを振り返っておきましょう。
%書式は"%sさん" % nameのように書く、Pythonでいちばん古い埋め込み方です。タプルを1つの値として渡すとTypeErrorになるので、(point,)のように包む必要があります。
str.format()は{}を目印にする書き方で、コロンのあとの書式指定はf-stringと共通です。テンプレートを先に用意できる点が強みですが、ユーザーが書いたテンプレートにはstring.Templateを使います。
そしてログ出力では、f-stringではなく%sと引数を分けて渡すのが基本です。これだけ押さえておけば、古いコードを読むときも、新しいコードを書くときも迷わなくなるはずです。
ここまでお読みいただきありがとうございました。
参考情報¶
- printf-style String Formatting(Built-in Types) — Python公式ドキュメント
- string — Common string operations(Format String Syntax / Template strings) — Python公式ドキュメント
- Logging HOWTO — Python公式ドキュメント
- PEP 3101 – Advanced String Formatting
- logging-fstring-interpolation / W1203 — Pylint documentation
- logging-f-string (G004) — Ruff documentation