3
4

Python f-strings ちゃんと知ってる?

Posted at

これは Python 3.12 時点での話:bug:
みんな知ってそうなことは書きません。

最初の方では !r, !s, = 指定子、そのあとに文字列と新機能の話。

基本

f-stringsのキホン
from datetime import datetime

animals = [
    "dog", "goat", "wombat", "cat",
]

# 大体のものは自然に文字列になる
print(f"animals: {animals}")
# animals: ['dog', 'goat', 'wombat', 'cat']

# 関数、演算、リスト内包表記...式の中で評価して文字列にしてくれる
print(f"now: {datetime.now()}")
# now: 2024-06-07 02:31:03.078175
print(f"{[2**n for n in range(1, 12)]}")
# [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048]

ちゃんと知ってる?な話

指定子 !r!s は repr と str

そもそも、あるオブジェクトが print や f-strings に評価されるとき、何をもって文字列になるのでしょうか。
JavaだったらtoString()ですが、Pythonだとそれは__str__()です。
似た存在として__repr__()もあります。

  • __str__:ユーザ:runner:が見て理解しやすい文字列
    • → f-strings の!s指定子で呼ばれる
    • → print()で呼ばれる
  • __repr__:デバグ:bug:に使う、それを見て再現できるような文字列
    • → f-strings の!r指定子で呼ばれる
    • → repr()で呼ばれる

たとえば datetime でいうと、こうなっています。
__str__の方が見やすくて、__repr__の方が開発者的かと思います。
こう作るものです。

f-string内の式でオブジェクトが評価されるときデフォルトで__str__()なので、!sは書く意味がありません

!sと!rと無印
# __str__() 略して 's'
print(f"{now!s}")
# 2024-06-07 02:31:03.078175

# __repr__() 略して 'r'
print(f"{now!r}")
# datetime.datetime(2024, 6, 7, 2, 31, 3, 78175)

# 通常呼ばれるのは's'の方
print(f"{now}")
# 2024-06-07 02:31:03.078175

指定子 =

デバグ用でいうと、=指定子もあります。
その変数のその時点での値を示すためのもの。

これは、その変数名 = __repr__()みたいな文字列になるものです。
変数名とその実体の__repr__がちゃんとしていれば、これがかなり強力。

print("status=", f"{status!r}")
# これは同じ出力
print(f"{status= }")

!r, !s, = 共存

指定子は共存することができます。
実際に実装して試してみると、こうなります。
この使い分けを意識してデバグしたり開発したりしたいです:bug:

f"{person!s}"f"{person=!r}" は頭痛が痛い的な感じ:ghost:

自作クラスと指定子の組み合わせ
class Person():
    def __repr__(self) -> str:
        return "__repr__"

    def __str__(self) -> str:
        return "__str__"

person = Person()
print(f"{person}")
# __str__
print(f"{person!s}")
# __str__
print(f"{person!r}")
# __repr__
print(f"{person=}")
# person=__repr__
print(f"{person=!s}")
# person=__str__
print(f"{person=!r}")
# person=__repr__

# `f"{person!s}"`と`f"{person=!r}"` は意味がない!

f-strings の実用的な例

ここからは実用的な例:factory:

!r, !s, = の実用的な例

  • =指定していれば、変数名がその文字列の意味を教えてくれる
  • reprはそれで再現可能な文字列 → 大体クラス名は出す

つまりこれからはType()を使う機会が減るかも。

かっこいいエラー処理
animals = ["dog", "goat", "wombat", "cat",]

try:
    animals[20]
except Exception as err:
    # ✕ これは冗長
    print(f"err: {err}, type: {type(err)}")
    # err: list index out of range, type: <class 'IndexError'>

    # ✕ これはraiseなのかcatchなのか分からない
    print(f"{err!r}")
    # IndexError('list index out of range')

    # 〇 `=`を使えば意味も型名も分かる
    print(f"{err=}")
    # err=IndexError('list index out of range')

こんなこともできる

リストを縦に並べて中央揃え

# Python 3.12 から式の中でバックスラッシュが使えるようになった
print(f"{'\n'.join(animals): ^12}")
#    dog
#    goat
#   wombat
#    cat

dict要素をわざわざ変数に逃がす必要はない

# Python 3.12 からダブルクオートの入れ子ができるようになった
dic = {
    "dog": "",
    "goat": "ヤギ",
}
print(f"goat: {dic["goat"]}")
# goat: ヤギ

改行もコメントも書ける

# '{}'内は構文中なので改行もコメントもできる
print(f"First animal is: {
    animals[0]  # コメントが書けるし
    # 改行もできる
}.")
# First animal is: dog.

入れ子

# f-strings の再利用が可能
txt = "いい例が思いつかない"
print(f"{f"{f"{txt}"}"}")
# いい例が思いつかない

リスト文字列の良い感じの整形

# 中央寄せ
for animal in animals:
    print(f"{animal: ^12}")
#     dog
#     goat
#    wombat
#     cat

# indexやkeyを右寄せする
for idx, animal in enumerate(animals):
    print(f"{idx+8: >2}. {animal.capitalize()}")
#  8. Dog
#  9. Goat
# 10. Wombat
# 11. Cat

strftime()はもう要らない

# 'datetime'を表示するとき、わざわざ'strftime()'する必要はない
now = datetime.now()
print(f"now: {now}")
# now: 2024-06-07 02:29:39.654739
print(f"now: {now:%Y年%m月%d日 %H時%M分}")
# now: 2024年06月07日 02時29分

=指定子の左右スペース

こう動くが、フォーマッタにスペースを消される:cold_sweat:

res = 600*30*12
print(f"{res=:,}")
# res=216,000
print(f"{res = :,}")
# res = 216,000
print(f"{res= :,}")
# res= 216,000

参考

ここにすべてが書いてある

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4