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

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

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

## 基本

```python: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`は書く意味がありません

```python:!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__`がちゃんとしていれば、これがかなり強力。

https://docs.python.org/3/whatsnew/3.8.html#f-strings-support-for-self-documenting-expressions-and-debugging

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

### !r, !s, = 共存

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

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

```python:自作クラスと指定子の組み合わせ
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()を使う機会が減るかも。

```python:かっこいいエラー処理
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
# Python 3.12 から式の中でバックスラッシュが使えるようになった
print(f"{'\n'.join(animals): ^12}")
#    dog
#    goat
#   wombat
#    cat
```

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

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

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

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

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

```python
# 中央寄せ
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()`はもう要らない

```python
# '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:

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

## 参考

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

https://docs.python.org/3/reference/lexical_analysis.html#f-strings

https://peps.python.org/pep-0498/

https://realpython.com/python-f-strings/