python3.9の新機能 辞書のマージ

# python3.9の新機能 辞書のマージ
## 概要
2020/10/05にPython3.9がリリースされました。今回の新機能の一つとして、辞書(dict)のマージ演算子があります。このマージ演算子を使うことで、これまでより簡単に辞書を併合できるようになります。

## お試し環境構築(要Docker)
以下の手順でPython3.9をDockerコンテナ上で実行できます。  
以下の手順はDockerコンテナ上で動させるため、ホストのPythonバージョンを更新せずにPython3.9を検証できます。

### 1. [docker hub](https://hub.docker.com)のアカウント作成(アカウントを持っていない場合のみ)。
### 2. ターミナルからdocker login  

```
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: # ここでdocker hubのユーザ名を入力してEnterキー
Password: # ここでdocker hubのパスワードを入力してEnterキー
```

以下の表示が出ればログイン成功

``` sh
Login Succeeded
```

### 3. python3.9のDocker imageをdocker hubからpullする

``` sh
$ docker pull python:3.9
 3.9: Pulling from library/python
e4c3d3e4f7b0: Pull complete 
101c41d0463b: Pull complete 
8275efcd805f: Pull complete 
751620502a7a: Pull complete 
0a5e725150a2: Pull complete 
397dba5694db: Pull complete 
b1d09d0eabcb: Pull complete 
475299e7c7f3: Pull complete 
d2fe14d8e6bc: Pull complete 
Digest: sha256:429b2fd1f6657e4176d81815dc9e66477d74f8cbf986883c024c9b97f7d4d5a6
Status: Downloaded newer image for python:3.9
docker.io/library/python:3.9
```

### 4. docker imageからコンテナを起動し、コンテナ内のPythonを対話モードで起動する

``` sh
$ docker run -it python:3.9 python3
Python 3.9.0 (default, Oct 13 2020, 20:14:06)  # ←Python3.9が起動している
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
```

## マージ演算子
2つの辞書を組み合わせて1つの辞書を作る場合を考えます。

``` python
>> dict_1 = {"name": "Alice", "age": 29}
>> dict_2 = {"mail": "alice@example.com", "tel": "000-0000-0000"}

# 上の2つのdictを組み合わせて以下のdictを作る
dict_3 = {'name': 'Alice', 'age': 29, 'mail': 'alice@example.com', 'tel': '000-0000-0000'}
```

従来のPythonでは、dictのitemsでループしてdict_2の要素を1つずつdict_1に代入していました。

``` python
>> dict_3 = dict_1.copy() # dict_3 = dict_1だとdict_1も書き換わるので注意
>>> for key, value in dict_2.items():
...   dict_3[key] = value
...
>>> dict_3
{'name': 'Alice', 'age': 29, 'mail': 'alice@example.com', 'tel': '000-0000-0000'}
>>> dict_1
{'name': 'Alice', 'age': 29} # マージ後もdict_1は変化しない
>>> dict_2
{'mail': 'alice@example.com', 'tel': '000-0000-0000'} # マージ後もdict_2は変化しない
```

Python3.9では上のような処理がマージ演算子だけで完結します

``` python
>>> dict_3 = dict_1 | dict_2 # | がマージ演算子
>>> dict_3
{'name': 'Alice', 'age': 29, 'mail': 'alice@example.com', 'tel': '000-0000-0000'}

```

マージ演算子を使うことで、dict_1とdict_2を併合する処理が1行で記述できます。  
2つのdictに同じキーが存在する場合は、マージ後の値は右辺のものになります(後勝ち)。

``` python
>>> dict_1 = {"name": "Alice", "age": "29"}
>>> dict_2 = {"mail": "alice@example.com", "tel": "000-0000-0000", "age": 30} # "age"がdict_1とdict_2の両方に含まれている
>>> dict_1 | dict_2
{'name': 'Alice', 'age': 30, 'mail': 'alice@example.com', 'tel': '000-0000-0000'} # ageがdict_2のものになっている
```
``` python
>>> dict_1 = {"name": "Alice", "age": "29"}
>>> dict_2 = {"mail": "alice@example.com", "tel": "000-0000-0000", "age": None}
>>> dict_1 | dict_2
{'name': 'Alice', 'age': None, 'mail': 'alice@example.com', 'tel': '000-0000-0000'} # 右辺の値がNoneでも併合後の辞書に入るので注意(左辺の値にはならない)

```
### マージ演算子の代入文
+や-などの他の演算子と同様、=をつけることでマージ後のdictを左辺へ代入することができます。

``` python
>>> dict_1 = {"name": "Alice", "age": 29}
>>> dict_2 = {"mail": "alice@example.com", "tel": "000-0000-0000"}
>>> id(dict_1), id(dict_2)
(140606638257856, 140606638701760)
>>> dict_1 |= dict_2 # dict_1とdict_2をマージした結果をdict_1に代入
>>> dict_1
{'name': 'Alice', 'age': 29, 'mail': 'alice@example.com', 'tel': '000-0000-0000'} # dict_1は書き換わる
>>> dict_2
{'mail': 'alice@example.com', 'tel': '000-0000-0000'} # dict_2は変化しない
>>> id(dict_1), id(dict_2)
(140606638257856, 140606638701760) # idは演算前後で同一
```

### マージ演算子とOrderedDict
マージ演算子はOrderedDict(順序付き辞書)に対しても使うことができます。

``` python
>>> from collections import OrderedDict
>>> o_dict_1 = OrderedDict({'name': 'Alice', 'age': 29})
>>> o_dict_2 = OrderedDict({'mail': 'alice@example.com', 'tel': '000-0000-0000'})
>>> o_dict_1
OrderedDict([('name', 'Alice'), ('age', 29)])
>>> o_dict_1 | o_dict_2
OrderedDict([('name', 'Alice'), ('age', 29), ('mail', 'alice@example.com'), ('tel', '000-0000-0000')])
```

左辺と右辺に共通するキーがある場合は、並び順は左辺、値は右辺のものになります。  
右辺にのみ含まれるキーは右辺の並び順のまま末尾に追加されます。

``` python
>>> from collections import OrderedDict
>>> o_dict_1 = OrderedDict({'name': 'Alice', 'age': 29})
>>> o_dict_2 = OrderedDict({'mail': 'bob@example.com', 'age': 30, 'name': 'Bob', 'tel': '000-0000-0000'})
>>> o_dict_1 | o_dict_2
OrderedDict([('name', 'Bob'), ('age', 30), ('mail', 'bob@example.com'), ('tel', '000-0000-0000')])
# age, nameはo_dict_1の順序でo_dict_2の値で並んでいる。
# dict_2のみに含まれるmail, telはo_dict_2の順番で末尾に追加される
```