はじめに
dict型というよりむしろxmlデータの構造をパッと確認したくなってしまったのが事の発端。
xmlファイルを辞書型で読み込み
サードパーティモジュールである、xmltodict
をインストールする
以下、XMLファイルを読み込んでxmltodictでパースした状態
#適当なファイルを読みたい場合
import xmltodict
with open(filename, encoding='utf-8') as fp:
xml_data = fp.read()
dict_data = xmltodict.parse(xml_data)
print(dict_data)
#とりあえず手っ取り早くサンプル。東京都の週間天気予報
import requests
import xmltodict
r=requests.get("https://www.data.jma.go.jp/developer/xml/data/20221220014352_0_VPFW50_130000.xml",verify=False)
r.encoding = r.apparent_encoding #<-元データがshift-jisなのでエンコード
dict_data=xmltodict.parse(r.text)
print(dict_data)
#{'Report': {'@xmlns': 'http://xml.kishou.go.jp/jmaxml1/',
# '@xmlns:jmx': 'http://xml.kishou.go.jp/jmaxml1/',
# '@xmlns:jmx_add': 'http://xml.kishou.go.jp/jmaxml1/addition1/',
# 'Control': {'Title': '府県週間天気予報',
# 'DateTime': '2022-12-20T01:43:50Z',
# 'Status': '通常',
# 'EditorialOffice': '気象庁本庁',
# ...
解答(イケてるやり方)
コメントにて@shiracamus 様にリファクタリングしていただきました
def func(data: dict, depth: int=0):
for key, value in data.items():
print("|" * depth, depth, key, type(value))
if isinstance(value, dict): # <-もし辞書型の値なら再帰で関数を呼び出す
func(value, depth + 1)
func(dict_data)
#0 Report <class 'dict'>
#|1 @xmlns <class 'str'>
#|1 @xmlns:jmx <class 'str'>
#|1 @xmlns:jmx_add <class 'str'>
#|1 Control <class 'dict'>
#||2 Title <class 'str'>
#||2 DateTime <class 'str'>
#||2 Status <class 'str'>
#||2 EditorialOffice <class 'str'>
#||2 PublishingOffice <class 'str'>
#|1 Head <class 'dict'>
#||2 @xmlns <class 'str'>
#||2 Title <class 'str'>
#||2 ReportDateTime <class 'str'>
#...
おわりに
自分用へのメモ。以下参考
以下参考
解答(ダサいやり方)
とりあえずprintで確認したかっただけなので、とりあえず以下のような関数を作り、再帰的に(関数内でもう一度自分自身を呼び出す)動かしてみる
def func(data,i=0):
keys=data.keys() #<-辞書型のkeysを取得して、それぞれの値のデータ型を確認する
for key in keys:
for _ in range(i): print("|",end="") # <- 階層に応じてパディングするために階層数分だけ'|'を描画する
print(i,key,type(data[key]))
if type(data[key])==dict: #<-もし辞書型ならもう一回関数を呼び出す、それ以外ならpassする
func(data[key],i+1)
else:
pass
func(dict_data)
解説(イケてる方とダサい方の比較)
供養のために解説。
型アノテーションを大事にする
引数として読み込みたい変数の型を明示すべき。これだけで可読性何十倍もアップする。
iとか頭悪い横着した変数名を使わず変数名のセンスを磨く鍛錬を常に意識する。
- def func(data,i=0):
+ def func(data: dict, depth: int=0):
辞書型のデータの読み出し方
keysだけを読み込んで、辞書型のデータを見に行くのはダサダサなので、最初からkeysとvaluesを読み込むとよい。
- keys=data.keys()
- for key in keys:
- print(i,key,type(data[key]))
+ for key,value in data.items():
+ print(i,key,type(value))
※解説。辞書型のデータを読み出す場合は以下の3通りがある。
data={"Hello":"world","Good":"morning"}
#keyだけを順番に読み込む、
>>> data.keys(),list(data.keys())
(dict_keys(['Hello', 'Good']), ['Hello', 'Good'])
#valuesだけを順番に読み込む
>>> data.values(),list(data.values())
(dict_values(['world', 'morning']), ['world', 'morning'])
#keyとvaluesの組合せをタプルとして順番に読み込む
>>> data.items(),list(data.items())
(dict_items([('Hello', 'world'), ('Good', 'morning')]), [('Hello', 'world'), ('Good', 'morning')])
数値以外のデータを乗算するテクニック
forループで回すなんかよりも、文字列を乗算したほうがよい。
- for _ in range(i): print("|",end="") # <- 階層に応じてパディングするために階層数分だけ'|'を描画する
+ print("|"*depth)
※解説、数値以外のデータを乗算すると、以下のような挙動になるので知っておく。
というかそもそもPythonを書いている間は、「如何にforループを避けられるか」を常に意識するべし。
#------------文字列------------------------
>>> "a"*5
'aaaaa'
>>> "abc"*5
'abcabcabcabcabc'
>>> ""*5
''
#------------リスト------------------------
>>> [1]*5
[1, 1, 1, 1, 1]
>>> [None]*5
[None, None, None, None, None]
>>> []*5
[]
#------------タプル------------------------
>>> (1,2)*5
(1, 2, 1, 2, 1, 2, 1, 2, 1, 2)
#------------辞書------------------------
>>> {"Hello":"World"}*5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for *: 'dict' and 'int'
型の比較にはisinstance(判別したい変数,データ型)
を使う
type(変数)==型
とかダサダサ。isinstance(判別したい変数,型)
を使う。
- if type(value)==dict:
+ if isinstance(value,dict):
まとめ
- def func(data,i=0):
+ def func(data: dict, depth: int=0): #<-型アノテーションと変数名のセンス
- keys=data.keys()
- for key in keys:
+ for key, value in data.items(): #<-dict型のデータの読み出し方、dict.items()でkeyとvalueを一緒にタプルで読む
- for _ in range(i): print("|",end="")
- print(i,key,type(data[key]))
+ print("|" * depth, depth, key, type(value)) #<-文字列データの乗算
- if type(data[key])==dict:
- func(data[key],i+1)
+ if isinstance(value, dict): #<-変数のデータ型の判別にはisinstance()を使う
+ func(value, depth + 1)
- else:
- pass
func(dict_data)
以上