#64　書式化演算子%とformatメソッドを比較してみた〜処理速度

## はじめに

前回、予定より文量が多くなってしまったため記載できなかった実際の処理の観点から、書式化演算子％とformatメソッドを比較していきたいと思います。

<br>

以前のブログで作成した処理をベースに、書式化演算子％とformatメソッドを用いたパターンに修正して、それぞれの処理速度には差があるのかを検証していきます。
合わせて、formatメソッドをformat済み文字列リテラルにした場合についても少し触れていこうと思います。

#### 本記事に関連するブログ記事

- 以前のブログ：[Kibelaの記事をBacklogのWikiへ移行する〜Backlog APIを利用する](https://qiita.com/nxted_sapporo/items/45dfbcbef8882e1d59f3)
- 前回のブログ：[書式化演算子%とformatメソッドを比較してみた〜構文と書式設定](https://qiita.com/nxted_sapporo/items/6fcfd02725d222df6657)

## 比較のための準備
今回比較に用いる処理として、こちらを利用していきます。
([以前のブログ記事](https://qiita.com/nxted_sapporo/items/45dfbcbef8882e1d59f3)より)

```python
# attachIdLists = [添付ファイルの送信APIから返却されたIDのリスト] 

def  backlogAddAttachment(wikiId, attachIdLists):
  attachStr = '&attachmentId[]='
    for attachIdList in attachIdLists:
      if attachIdList == attachIdLists[0]:
        # format メソッド
        attachmentId = '{}{}'.format(attachStr, attachIdList)
      else:
        # format メソッド
        attachmentId = '{}{}{}'.format(attachmentId, attachStr, attachIdList)
    # 書式化演算子%
    uploadUrl = "%s/api/v2/wikis/%s/attachments?apiKey=%s%s" % (url, wikiId, apikey, attachmentId)
    requests.post(uploadUrl).json()
```
上記の処理では書式化演算子％とformatメソッドが混在しているので、まずはそれぞれのパターンに分けた処理内容に修正します。
<br>
**書式化演算子％ に統一**

```python
# attachIdLists = [添付ファイルの送信APIから返却されたIDのリスト] 

def  backlogAddAttachment(wikiId, attachIdLists):
  attachStr = '&attachmentId[]='
    for attachIdList in attachIdLists:
      if attachIdList == attachIdLists[0]:
        attachmentId = "%s%s" % (attachStr, attachIdList)
      else:
        attachmentId = "%s%s%s" % (attachmentId, attachStr, attachIdList)
    uploadUrl = "%s/api/v2/wikis/%s/attachments?apiKey=%s%s" % (url, wikiId, apikey, attachmentId)
    requests.post(uploadUrl).json()
```

<br>

**formatメソッド に統一**

```python
# attachIdLists = [添付ファイルの送信APIから返却されたIDのリスト] 

def  backlogAddAttachment(wikiId, attachIdLists):
  attachStr = '&attachmentId[]='
    for attachIdList in attachIdLists:
      if attachIdList == attachIdLists[0]:
        attachmentId = '{}{}'.format(attachStr, attachIdList)
      else:
        attachmentId = '{}{}{}'.format(attachmentId, attachStr, attachIdList)
    uploadUrl = '{}/api/v2/wikis/{}/attachments?apiKey={}{}'.format(url, wikiId, apikey, attachmentId)
    requests.post(uploadUrl).json()
```

<br>
しかし、今回の検証ではBacklogAPIにリクエストを送る必要はありません。それよりも、処理時間が把握できるようにログを出力させる処理を加えたいところです。

そのため今回は、検証用に修正した以下の処理を用いて比較していきたいと思います。

#### 今回使用するスクリプト

```python
attachStr  =  '&attachmentId[]='
attachIdLists  = [1, 2, 3, 4, 5]
url  =  'A'
wikiId  =  'B'
apikey  =  'C'

flg = True # True：書式化演算子％での処理
forking(flg, attachStr, attachIdLists, url, wikiId, apikey)

flg = False # False：formatメソッドでの処理
forking(flg, attachStr, attachIdLists, url, wikiId, apikey)

# 書式化演算子％を用いた処理（計測したい処理）
def  shoshikika(attachStr, attachIdLists, url, wikiId, apikey):
  for  attachIdList  in  attachIdLists:
    if  attachIdList  ==  attachIdLists[0]:
      attachmentId  =  "%s%s"  % (attachStr, attachIdList)
    else:
      attachmentId  =  '%s%s%s'  % (attachmentId, attachStr, attachIdList)
  uploadUrl  =  "%s/api/v2/wikis/%s/attachments?apiKey=%s%s"  % (url, wikiId, apikey, attachmentId)

# formatメソッドを用いた処理（計測したい処理）
def  format(attachStr, attachIdLists, url, wikiId, apikey):
  for  attachIdList  in  attachIdLists:
    if  attachIdList  ==  attachIdLists[0]:
      attachmentId  =  '{}{}'.format(attachStr, attachIdList)
    else:
      attachmentId  =  '{}{}{}'.format(attachmentId, attachStr, attachIdList)
  uploadUrl  =  '{}/api/v2/wikis/{}/attachments?apiKey={}{}'.format(url, wikiId, apikey, attachmentId)

# 取得した日時から時分秒コンマを返す
def  replaceTime(now):
  return  int(now.strftime("%H%M%S"  +  str(now.microsecond)))

# １００万回繰り返した時の処理時間を出力
def forking(flg, attachStr, attachIdLists, url, wikiId, apikey):
  # 処理開始時の日時を取得する
  startTime  =  replaceTime(datetime.datetime.now())
  for  i  in  range(1000000):
    if  flg:
      shoshikika(attachStr, attachIdLists, url, wikiId, apikey)
    else:
      format(attachStr, attachIdLists, url, wikiId, apikey)
  # 処理終了時の日時を取得し、処理開始時との差分を求める
  endTime  =  replaceTime(datetime.datetime.now())
  time  =  endTime  -  startTime
  print("処理時間 : "  +  str(time) +  "ms")
```

## 検証結果
スクリプトを5回実行した結果と、その平均処理時間を表にまとめてみました。

|回数|書式化演算子％|formatメソッド|
|----|----|----|
|1|690,921 ms|943,157 ms|
|2|693,923 ms|945,866 ms|
|3|696,737 ms|951,095 ms|
|4|696,715 ms|942,355 ms|
|5|694,569 ms|960,783 ms|
|**平均**|**694,573 ms**|**948,651 ms**|

意外にも、書式化演算子％を用いた処理の方が速いという結果になりました。
<br>
しかし、上記で使用しているのは書式化演算子％・formatメソッドの最も基本的な構文です。

せっかくなので、オプションを指定した場合に処理時間がどのように変化するのか、確認してみましょう。
また、format() をformat済み文字列リテラルにしたパターンも作成し、その結果を比較してみました。
<br>
format済み文字列リテラルの詳細については下記をご参考ください。
https://note.nkmk.me/python-f-strings/

#### オプション付きおよびformat済み文字列リテラルでの検証

基本的には上で示したスクリプトをそのまま使用し、変更する際は shoshikika() または format() 関数内のみの修正としました。
以下では、修正した箇所のみ改めてコードで示しています。
また、引数として渡す値の内、attachStr と attachIdLists は既存を固定値とし、url・wikiId・apikey については検証パターンごとに渡す値を記載しています。
<br>
スクリプトの実行回数は5回、以降の検証結果ではそれらの平均値を比較対象としています。
<br>

**マップ型**

```python
url = 'A'
wikiId = 'B'
apikey = 'C'

# 書式化演算子％
uploadUrl  =  "%(c)s/api/v2/wikis/%(a)s/attachments?apiKey=%(d)s%(b)s"  % {"a":url, "b":wikiId, "c":apikey, "d":attachmentId}

# formatメソッド
uploadUrl  =  '{c}/api/v2/wikis/{a}/attachments?apiKey={d}{b}'.format(a=url, b=wikiId, c=apikey, d=attachmentId)

# f文字列
uploadUrl  =  f'{apikey}/api/v2/wikis/{url}/attachments?apiKey={attachmentId}{wikiId}'
```

|書式化演算子％|formatメソッド|f文字列|
|----|----|----|
|841,268 ms|1059013 ms|837,677 ms|

<br>

**16進数**

```python
url = 30
wikiId = 543
apikey = 999

# 書式化演算子％
uploadUrl  =  "%x/api/v2/wikis/%x/attachments?apiKey=%x%s"  % (url, wikiId, apikey, attachmentId)

# formatメソッド
uploadUrl  =  '{:x}/api/v2/wikis/{:x}/attachments?apiKey={:x}{}'.format(url, wikiId, apikey, attachmentId)

# f文字列
uploadUrl  =  f'{url:x}/api/v2/wikis/{wikiId:x}/attachments?apiKey={apikey:x}{attachmentId}'
```

|書式化演算子％|formatメソッド|f文字列|
|----|----|----|
|743,941 ms|982,312 ms|1,046,048 ms|

<br>

**10進浮動小数点数(固定小数点数表記)＋小数点以下3桁まで**

```python
url = 0.1234567
wikiId = 4.890
apikey = 999.9

# 書式化演算子％
uploadUrl  =  "%.3f/api/v2/wikis/%.3f/attachments?apiKey=%.3f%s"  % (url, wikiId, apikey, attachmentId)

# formatメソッド
uploadUrl  =  '{:.3f}/api/v2/wikis/{:.3f}/attachments?apiKey={:.3f}{}'.format(url, wikiId, apikey, attachmentId)

# f文字列
uploadUrl  = f'{url:.3f}/api/v2/wikis/{wikiId:.3f}/attachments?apiKey={apikey:.3f}{attachmentId}'
```

|書式化演算子％|formatメソッド|f文字列|
|----|----|----|
|964,198 ms|1,198,392 ms|1,245,37 ms|

いかがでしたでしょうか？
書式化演算子%とformatメソッドの処理時間の差に大きな違いは見られませんでした。
f文字列はマップ型の処理が得意なようですね。


## おわりに
今回は書式化演算子％とformatメソッドを中心に、処理速度の比較検証を行なってみました。
個人的にはformatメソッドの方が速いと予想していましたが、実際に検証してみると書式化演算子％の方が速いとわかり、面白かったです。
今回は同じ言語内での比較でしたが、機会があれば言語別で処理時間を比較してみても楽しそうですね。
<br>
最後まで閲覧いただきありがとうございます。
<br><br>
参考：
 - https://note.nkmk.me/python-f-strings/