5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-12-03

概要

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

ざっくり公式ページの内容を要約しますと、
Python2で辞書型の変数をvalues()items()keys()メソッドで取得すると結果がリスト型で取得できていたが、Python3では、辞書ビューオブジェクトが返されるようになりました。
listフィルタを追加することでPython2と同じ結果が得られるだそうです。

さっそくコード

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

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]
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]
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() }}"

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

実行結果(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で出力されています。
なので、この結果をループしたりすると動作が変わるので、要注意です。

実行結果(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フィルタを追加するのも良いかと思います。(出力結果は変わりません)

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 }}"
5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?