Pythonのf-stringsについてしっかり調べてみた

Pythonの文字列関係の制御を調べていて、f-strings（f文字列）が結構奥が深く知らないことも多かったので、別途Qiita記事にまとめておきます。

# そもそもf-stringsってなに？

Pythonで文字列の前に`f`という文字を付与し、且つ`{}`の括弧を使うことで文字列中に変数を展開したりコードを実行したりできる機能です。

以下のようなコードになります。

```py
name = 'タマ'
txt = f'猫の名前は{name}です。'
print(txt)
```

```
猫の名前はタマです。
```

# 主な参考サイト

f-stringsについて詳しく説明がされている[PEP 498 -- Literal String Interpolation](https://www.python.org/dev/peps/pep-0498/)を主に参考にします。

# 記事上で使う環境

- Windows10
- Python 3.8.0
- Jupyter（実行にはVS CodeのPython拡張機能を利用）

# f-stringsを使うと何が嬉しいのか？

f-stringsを使ってできることの大半（変数の挿入処理など）は文字列のformatメソッドでも実現することができます。

一方でformatメソッドだと記述が少し長くなりがちです。

`%`オペレーターを使った短い記述による挿入方法もありますが、要素が多くなってきたりすると読みづらくなったりしてしまいます。また、数値や文字列などとタプルで挙動が変わるので、変数の型によって予期せぬエラーになってしまうという点もあります。

その辺りをうまい具合に対応したのがf-stringsです。記述が短く、且つ読みやすく書けて、そしてタプルなどの型によって挙動が変わったりしません。

※`%`オペレーターによる記述やformatメソッドの代替となるものではなく、ケースに応じて適したものを使い分ける形の機能とされています。`%`オペレーターやformatメソッドがdeprecated指定されたり将来切り落とされる類のものではないとPEP上で説明されています。

# f-stringsでできる主なこと

## 文字列中に変数の値を挿入する

文字列の前にfの文字の付与と`{}`の記号を使うだけで前述のコードのように変数を挿入することができます。別途formatメソッドを書いたりも不要になるためコード量が少なくなります。

整数や文字列などだけでなく、`%`オペレーターとは異なりタプルなどの変数でもそのまま（文字列キャストなど無しに）挿入することができます。

```py:整数の変数を挿入するサンプル
age = 5
print(f'歳は{age}歳です。')
```

```
歳は5歳です。
```

```py:タプルの変数を挿入するサンプル
tpl = ('タマ', 'ミケ')
print(f'タプルの値は{tpl}です。')
```

```
タプルの値は('タマ', 'ミケ')です。
```

## 文字列中で計算などもできる

例えば変数に保持するものでもない（使い捨ての値など）ケース等に、文字列中でPythonでの加算をはじめとした様々な処理や計算を実行することができます。formatメソッドでは引数箇所でそのまま計算などしてしまえばいいのですが、f-stringsではメソッドを書いたりしないためこのような処理が可能になっています。

```py:文字列中でインクリメントするサンプル
age = 5
print(f'来年は{age + 1}歳になります。')
```

```
来年は6歳になります。
```

## 関数の実行などもできる

f-stringsでは変数などが文字列中に展開できるだけでなく、大半のPythonのコードを実行することができます。例えば関数なども呼び出すことができます。返却値を変数に持たずに結果を文字列へ反映することができます。

```py:文字列中で関数を実行するサンプル
def get_name():
    return 'タマ'


print(f'猫の名前は{get_name()}です。')
```

```
猫の名前はタマです。
```

もちろんキャストなどもいけます。

```py:文字列中で整数へのキャストを行うサンプル
price = 105 * 1.1
print(f'値段は{int(price)}円です。')
```

```
値段は115円です。
```

## フォーマットを指定できる

`%`オペレーターやformatメソッドでも使われる、フォーマット（`__format__`のメソッドのformat_spec的な値による制御）を、formatメソッドなどと同じように利用することができます。

使うには半角のコロンを変数などの右に置いて、さらにその右にformat_specの値を指定します（例 : `:.3f`など）。

```py:小数点以下第三位までのフォーマットを指定するサンプル
float_value = 123.4
print(f'現在の値は{float_value:.3f}です。')
```

```
現在の値は123.400です。
```

## `{}`の括弧を入れ子にすることで、format_specに指定する部分なども変数を反映することができる

例えば小数点以下第n位まで表示するフォーマットで、nの桁数を変数を参照させる場合などに、`{}`の括弧内でさらに`{}`の括弧を入れ子にすることで対応することができます。

```py:小数点以下の桁数に変数を指定するサンプル
n_digits_after_floating_point = 1
float_value = 123.456789

print(f'現在の値は{float_value:.{n_digits_after_floating_point}f}です。')
```

```
現在の値は123.5です。
```

```py:format_specの指定を直接文字列で行うサンプル
format_spec = '.3f'
float_value = 123.456789

print(f'現在の値は{float_value:{format_spec}}です。')
```

```
現在の値は123.457です。
```

# f-stringsにおけるエスケープ

f-stringsの文字列中で、`{}`の括弧の記号をそのまま出力した場合にバックスラッシュでのエスケープは利きません。試してみるとエラーになります。


```py:エスケープ用にバックスラッシュを指定するとエラーになります
f'\{吾輩は猫である。\}'
```

```
SyntaxError: f-strings expression part cannot include a backslash
```

もし`{}`の括弧をエスケープしたい場合には`{}`の括弧を二重（`{{}}`といったように）書くとエスケープすることができます。

```py:f-strings中の{}の括弧をエスケープして結果の文字列に残すサンプル
print(f'{{吾輩は猫である。}}')
```

```
{吾輩は猫である。}
```

シングルクォート（もしくはダブルクォート）での文字列中にシングルクォートを使いたい場合などでも、バックスラッシュによるエスケープが使えないので、別の引用符を使う必要があります。例えば文字列中にシングルクォートが必要なケースでは全体はダブルクォートで囲うなどの対応が必要になります。

```py:文字列中に必要な引用符とは別の引用符を使って囲む例
print(f"吾輩は'猫'である。")
```

```
吾輩は'猫'である。
```

もしくは三重引用符（`"""`）を活用します。

```py:
print("""吾輩は"猫"である。""")
```

```
吾輩は"猫"である。
```

# 引用符を分割した文字列の連結時には挙動はどうなる？

Pythonでは横に長くなる文字列などで、以下のように引用符を区切って間に改行を入れても1つの文字列に連結してくれます。長い文字列で読みやすくするため（もしくはPEP8などに準拠するため）に改行が必要な時などに便利です。

```py:引用符を区切って改行を入れる例
txt = (
    '吾輩は猫である。まだ名は無い。'
    '何でも薄暗いじめじめした所でニャーニャー泣いていた'
    '事だけは記憶している。'
)
print(txt)
```

```
吾輩は猫である。まだ名は無い。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。
```

区切った際のf-stringsの指定は、`{}`の括弧が含まれているものだけfの文字の指定が必要になります。他は指定しても省略しても、問題なく連結が実行されます。

```py:真ん中の文字列だけfの文字をしている例
meow = 'ニャーニャー'
txt = (
    '吾輩は猫である。まだ名は無い。'
    f'何でも薄暗いじめじめした所で{meow}泣いていた'
    '事だけは記憶している。'
)
print(txt)
```

```
吾輩は猫である。まだ名は無い。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。
```

# 文字列中のPythonの処理が長い場合は・・・

文字列中の`{}`内の改行などの空白文字は無視されるので、その中であれば改行を入れてPythonのコードを書いても結果の文字列側には影響が出ません。

```py:文字列中のPythonコードで改行を入れる例
def get_value(x, y, z):
    return x * y * z


txt = f"""値は{
get_value(
    x=10,
    y=20,
    z=30,
)}です。
"""
print(txt)
```

```
値は6000です。
```

まあでも複雑なもので多用すると読みづらくなりそう（VS Code上で試していたら、文字列中でもちゃんと補完などは効いていましたが・・・）なので、程ほどに・・・という感じでしょうか。