Python2からPython3の切り替えたらdict valuesに気をつけよう！

# 概要

Python2のサポート終了からそろそろ1年経とうとしていますが、Python3に切り替えたことによって`dict.values`の挙動が変わりましたので、改めて紹介したいと思います。
というのも、書き方によってはPython2時代と同じ挙動するので、そもそも以下で説明する問題に気付いていない可能性もあります。

ざっくり[公式ページ](https://docs.ansible.com/ansible/latest/user_guide/playbooks_python_version.html)の内容を要約しますと、
Python2で辞書型の変数を`values()`、`items()`、`keys()`メソッドで取得すると結果が**リスト型**で取得できていたが、Python3では、[辞書ビューオブジェクト](https://docs.python.org/ja/3/library/stdtypes.html?highlight=dict#dictionary-view-objects)が返されるようになりました。
**listフィルタ**を追加することでPython2と同じ結果が得られるだそうです。

# さっそくコード

Python3ではブロックスタイルと通常のスタイルで結果が異なるので、大丈夫だと思っていても、書き方(通常のスタイル)を変えただけで意図しない結果を招く可能性があります。
ちなみに`ansible_python_interpreter`で挙動が変わるわけではなく、ansibleそのものがPython2.xかPython3.xによって挙動が変わります。

```shell:Python3実行環境

# 設定なし
> ansible-config view
ERROR! Invalid or no config file was supplied

# Python3版 ansible
> ansible --version

ansible 2.9.14
  config file = None
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.9/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.9.0 (default, Nov 18 2020, 13:28:38) [GCC 8.3.0]
```


```shell:Python2実行環境

# 設定なし
> ansible-config view
ERROR! Invalid or no config file was supplied

# Python2版 ansible
> ansible --version

ansible 2.9.14
  config file = None
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python2.7/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.18 (default, Apr 20 2020, 19:27:10) [GCC 8.3.0]
```


```yaml:dict_values.yml

---
- name: Dict values 
  hosts: localhost
  connection: local
  gather_facts: False
  vars:
    mydict: {"a": {"aa": 1 }, "b": {"bb": 2 }, "c": {"cc": 3 }}
  tasks:
    - name: block style debug
      debug:
        msg: >-
          {%- for d in mydict.values() -%}
            {{ d }}
          {%- endfor -%}

    - name: normal style debug
      debug:
        msg: "{{ item }}"
      with_items: "{{ mydict.values() }}"
```

ブロックスタイルと通常の結果が全く同じ

```shell:実行結果(python2)

TASK [debug] *************************************************************************
ok: [localhost] => {
    "msg": "{u'aa': 1}{u'cc': 3}{u'bb': 2}"
}

TASK [debug] *************************************************************************
ok: [localhost] => (item={u'aa': 1}) => {
    "msg": {
        "aa": 1
    }
}
ok: [localhost] => (item={u'cc': 3}) => {
    "msg": {
        "cc": 3
    }
}
ok: [localhost] => (item={u'bb': 2}) => {
    "msg": {
        "bb": 2
    }
}
```

通常の結果が`dict_values`で出力されています。
なので、この結果をループしたりすると動作が変わるので、要注意です。

```shell:実行結果(python3)
TASK [debug] *************************************************************************
ok: [localhost] => {
    "msg": "{'aa': 1}{'bb': 2}{'cc': 3}"
}

TASK [debug] *************************************************************************
ok: [localhost] => (item=dict_values([{'aa': 1}, {'bb': 2}, {'cc': 3}])) => {
    "msg": "dict_values([{'aa': 1}, {'bb': 2}, {'cc': 3}])"
}
```

# 対策

listフィルタを追加することで解決します。
全体のコードの整合性を保つためにブロックスタイルにもlistフィルタを追加するのも良いかと思います。（出力結果は変わりません）

```yaml:dict_values.yml

---
- name: Dict values 
  hosts: localhost
  connection: local
  gather_facts: False
  vars:
    mydict: {"a": {"aa": 1 }, "b": {"bb": 2 }, "c": {"cc": 3 }}
  tasks:
    - name: block style debug
      debug:
        msg: >-
          {%- for d in mydict.values() | list -%}
            {{ d }}
          {%- endfor -%}

    - name: normal style debug
      debug:
        msg: "{{ item }}"
      with_items: "{{ mydict.values() | list }}"
```