概要
Python2のサポート終了からそろそろ1年経とうとしていますが、Python3に切り替えたことによってdict.values
の挙動が変わりましたので、改めて紹介したいと思います。
というのも、書き方によってはPython2時代と同じ挙動するので、そもそも以下で説明する問題に気付いていない可能性もあります。
ざっくり公式ページの内容を要約しますと、
Python2で辞書型の変数をvalues()
、items()
、keys()
メソッドで取得すると結果がリスト型で取得できていたが、Python3では、辞書ビューオブジェクトが返されるようになりました。
listフィルタを追加することでPython2と同じ結果が得られるだそうです。
さっそくコード
Python3ではブロックスタイルと通常のスタイルで結果が異なるので、大丈夫だと思っていても、書き方(通常のスタイル)を変えただけで意図しない結果を招く可能性があります。
ちなみにansible_python_interpreter
で挙動が変わるわけではなく、ansibleそのものがPython2.xかPython3.xによって挙動が変わります。
# 設定なし
> 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]
# 設定なし
> 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]
---
- 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() }}"
ブロックスタイルと通常の結果が全く同じ
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
で出力されています。
なので、この結果をループしたりすると動作が変わるので、要注意です。
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フィルタを追加するのも良いかと思います。(出力結果は変わりません)
---
- 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 }}"