pythonの辞書

辞書とは

pythonで連想配列やマップに相当するものは、辞書 (dictionary) です。ユニークな(重複できない)キーに対して値を対応させるものです。キーと値をkey,valueとしておきます。値という言葉を変数の値などで使用することがありますのでそれと区別するためです。

keyやvalueの型を予め決める必要はありません。項目ごとに混在させられます。マニュアルには「ハッシュ可能 (hashable) な値を任意のオブジェクトに対応付ける」とあります。

pythonにはmapという名前の関数がありますが、これはJavaなどのMapとは別物です。リストの各要素に指定した関数を適用する関数です。

辞書の生成とvalueの変更・項目の追加

keyとvalueの組を辞書の項目といいます。key:value を項目としてコンマで区切り、{ }の中に並べ辞書の生成と初期値の代入をします。

辞書名[key]でvalueの参照です。keyがなければ KeyError になり、プログラムが停止します。

辞書名.get(key[, default])メソッドでも参照できます。辞書内にkeyに一致するものが存在しなければdefaultを、defaultも省かれていれば None を返します。

辞書名[key]=value でkeyがすでに存在すればvalueの変更、しなければ項目追加になります。

fruits = {"apple":"りんご", "orange":"みかん", "peach":"もも"}
print (fruits)
print (fruits["apple"])
print (fruits.get("apple"))
#print (fruits["lemon"])    #KeyError 'lemon'
print (fruits.get("lemon")) #None
print (fruits.get("lemon","lemon not exist as key")) 
fruits["orange"] = "オレンジ"
print (fruits)
print (fruits["orange"])
fruits["pineapple"] = "パイナップル"
print (fruits)
print (len(fruits))

実行結果

{'peach': 'もも', 'orange': 'みかん', 'apple': 'りんご'}
りんご
りんご
None
lemon not exist as key
{'peach': 'もも', 'orange': 'オレンジ', 'apple': 'りんご'}
オレンジ
{'peach': 'もも', 'orange': 'オレンジ', 'apple': 'りんご', 'pineapple': 'パイナップル'}
4

len(辞書名)は項目の数を返します。

""と''はpythonではどちらを使っても同じと言われますが、print(辞書名)では' 'で表示されます。

valueの型は混在可

ここではvalueの方に様々な型の変数やオブジェクトを格納できることを示します。簡単にするためkeyはすべて文字列にしています。

"num"のところを見ると、keyに同じものがあると後に書いたものでvalueが上書きされることもわかります。

fruits = {"apple":"りんご", "orange":"みかん", "peach":"もも"}
names = ["mike","tama","kuro"]
ntuple = ("pochi","shiro","koro","masaru")
iroirodic = {"list":names, "tuple":ntuple, "dic":fruits, "num":123, "num":4.5}
print (iroirodic["list"])
iroirodic["dic"]["pineapple"] = "パイナップル"
print (iroirodic["dic"])
print (fruits)
print (type(iroirodic["list"]))
print (type(iroirodic["tuple"]))
print (type(iroirodic["dic"]))
print (type(iroirodic["num"]))
print (iroirodic["num"])
print (type(iroirodic))

実行結果

['mike', 'tama', 'kuro']
{'peach': 'もも', 'orange': 'みかん', 'apple': 'りんご', 'pineapple': 'パイナップル'}
{'peach': 'もも', 'orange': 'みかん', 'apple': 'りんご', 'pineapple': 'パイナップル'}
<class 'list'>
<class 'tuple'>
<class 'dict'>
<class 'float'>
4.5
<class 'dict'>

keyの型も混在可

次にkeyの方に様々な型を使ってみます。実用では、文字列か数値(それも整数)を使うことになると思いますので、半分はお遊びの実験です。簡単にするためvalueはすべて文字列にしています。

前半では数値と文字列を区別することが確かめられます。22と"22"は別のものです。

変数をkeyにすると、変数の値をkeyにすることがわかります。[ ]に計算式を入れると計算結果がkeyになります。

var1 = 333
var2 = "aaa"
iroirodic = {22:"number", "22":"string", var1:"int in var",var2:"str in var"}
print (iroirodic[22])      #number
print (iroirodic["22"])    #string
print (iroirodic[var1])    #int in var
print (iroirodic[333])     #int in var
print (iroirodic[111+222]) #int in var
print (iroirodic[var2])     #str in var
print (iroirodic["aaa"])    #str in var
print (iroirodic["a"+"aa"]) #str in var
var2 = "bbb"
print (iroirodic.get(var2,"no key "+var2))  #no key bbb
var2 = "a"*3
print (iroirodic[var2])  #str in var
var3 = var2
var2 = "ccc"
print (iroirodic[var3])  #str in var

プログラム中にもコメントに出力を書きましたが、実行結果は

number
string
int in var
int in var
int in var
str in var
str in var
str in var
no key bbb
str in var
str in var

keyの型も混在可-その2

「ハッシュ可能 (hashable) な値」の禮として、int, str, tuple, frozenset が挙げられていたのでtupleを使ってみます。これらは、immutableなので hashableなのですが、今のところは使ってみるにとどめます。

リストや辞書は keyにすることができません。TypeError: unhashable type: 'dict' などと叱られます。

ntuple = ("pochi","shiro","koro","masaru")
iroirodic = {(2,5):"tuple1", ntuple:"tuple2"}
print (iroirodic[(2,5)])  #tuple1
print (iroirodic[ntuple]) #tuple2
print (iroirodic.get(("pochi", "shiro", "kuro", "masaru"),"no key for tuple2")) #no key for tuple2
print (iroirodic.get(('pochi', 'shiro', 'koro', 'masaru'),"no key for tuple2")) #tuple2
print (iroirodic.get(('pochi','shiro','koro','masaru'),"no key for tuple2"))    #tuple2
ntuple = ('pochi', 'shiro', 'koro', 'masaru')
print (iroirodic[ntuple])

プログラム中にもコメントに出力を書きましたが、実行結果は

tuple1
tuple2
no key for tuple2
tuple2
tuple2
tuple2

intやStringと同様な動きをします。ただ、文字列のタプルでは ' ' でないと同一と判断してもらえない様です。" " でも ' ' でも同じと聞いていたので意外です。

for文で繰り返し

Java同様Keyだけ、valueだけ、key-valueの組の集合を得る方法があり、それを一つずつ処理することができます。

得られる「集合」はリストやタプルではなく、それぞれ 'dict_keys'、'dict_values'、'dict_items' と別々のクラスのインスタンスです。

3ついっぺんに使ってみます。

fruits = {"apple":"りんご", "orange":"みかん", "peach":"もも"}
print ("(1) using keys()")
for key in fruits.keys():
   print ("Key:"+key+" Value:" + fruits.get(key))
print ("(2) using values()")
for val in fruits.values():
   print ("Value:" + val)
print ("(3) using items()")
for (k,v) in fruits.items():
   print ("Key:"+k+" Value:" + v)
print(type(fruits.keys()))
print(type(fruits.values()))
print(type(fruits.items()))

実行結果

(1) using keys()
Key:peach Value:もも
Key:orange Value:みかん
Key:apple Value:りんご
(2) using values()
Value:もも
Value:みかん
Value:りんご
(3) using items()
Key:peach Value:もも
Key:orange Value:みかん
Key:apple Value:りんご
<class 'dict_keys'>
<class 'dict_values'>
<class 'dict_items'>

辞書の項目順序

バージョン 3.7 で辞書の項目の順序が挿入順のままに固定されるようになったとのことです。3.6まででは次のように変わってしまいます。

fruits = {}
fruits['apple']='りんご'
fruits['lemon']='レモン'
fruits['orange']='みかん'
print(fruits)
for (k,v) in fruits.items():
   print ("Key:"+k+" Value:" + v)

実行結果は、

{'lemon': 'レモン', 'orange': 'みかん', 'apple': 'りんご'}
Key:lemon Value:レモン
Key:orange Value:みかん
Key:apple Value:りんご

連想配列に感動した昔

javascript版,Java版と同じ企画です。

連想配列に出会ったのは1993年のことでした。UNIX上のawkでした。

次のような名前と数値が組になったデータから、名前ごとの合計を出すというものです。

onion 50
carrot 226
carrot 315
eggplant 30
radish 75
eggplant 30
onion 120
carrot 417
eggplant 23
onion 80
radish 89
carrot 715
onion 95
radish 135
radish 98

こんな感じでできてしまいます。

$ awk '{sum[$1]+=$2} END {for(name in sum)print name,sum[name]}' onion.txt 
radish 397
eggplant 83
carrot 1673
onion 345

END以下は結果の表示のためのforで、計算の本体は連想配列 sum[$1] += $2 です。 $1がkey、$2が値でkeyが同じ場所に値を加算するものです。

Pythonの辞書を使ってやってみる

Pythonでもファイルから一度に全部を読む方法がありますからファイルが大きくない場合はこれを使います。Javaでは、Files.readAllLines(path)でしたが、Pythonでは、ファイルオブジェクト.readlines() です。

データファイルは、上記の onion 50\n carrot 226\n carrot 315\n... とスペース区切りのテキストファイル。onion.txt という名前でプログラムを実行するディレクトリに置きます。今回はわざと226の前にスペースを2つ入れています。

データは\nがついたまま行ごとのリストになってlinesに格納されます。

次のforで1行ずつ処理されます。

split()で引数を略して空白文字(\nも\tもその連続にも対応する)で区切り、key,valueのタプルにします。split()の戻値はリストです。lineが空行だったり、(k,v)でなかったりすることは想定しません。

get(k,0)はkを探して、vを得ますが、辞書にkが含まれなてときは0を返します。このメソッドのおかげでJavascriptやJavaよりawkに近いものになっています。

for文は上で3種を示したので、ちょっと変えてitems()をtupleで受け、format % values の valuesに使うという方法を取りました。printf 形式のformatはあまり好きではないですが、Javaでも同様にできるのでここではこれを採用しておきます。

datafile = open("onion.txt", "r")
lines = datafile.readlines() #行ごとのリストにする
datafile.close()
print(lines) #\nがついていることの確認
oniondict = {}
for line in lines:
    (k,v) = line.split()
    print ("Key:"+k+" Value:" + v) #splitの引数を略すると空白文字(\nも\tもその連続にも対応する)
    oniondict[k]=oniondict.get(k,0)+int(v)
print(oniondict) #辞書の確認
for kandv in oniondict.items():
    # kandv はtuple
    print ('%-9s:%4d' % kandv)

実行結果

['onion 50\n', 'carrot  226\n', 'carrot 315\n', 'eggplant 30\n', 'radish 75\n', 'eggplant 30\n', 'onion 120\n', 'carrot 417\n', 'eggplant 23\n', 'onion 80\n', 'radish 89\n', 'carrot 715\n', 'onion 95\n', 'radish 135\n', 'radish 98\n']
Key:onion Value:50
Key:carrot Value:226
Key:carrot Value:315
Key:eggplant Value:30
Key:radish Value:75
Key:eggplant Value:30
Key:onion Value:120
Key:carrot Value:417
Key:eggplant Value:23
Key:onion Value:80
Key:radish Value:89
Key:carrot Value:715
Key:onion Value:95
Key:radish Value:135
Key:radish Value:98
{'onion': 345, 'carrot': 1673, 'radish': 397, 'eggplant': 83}
onion    : 345
carrot   :1673
radish   : 397
eggplant :  83

薄い色にした部分は、処理途中の確認のためです。改行や空白文字など動作確認が必要なものが多くありました。残しておくほうが参考になると考えました。

Pythonの辞書を使ってやってみる その2

withを使ったファイルのopen、空行などのチェック、printf似でなくformatを使った書式出力を取り入れてもう一つ例を出しておきます。

with open では、closeが不要です。

データファイルにありがちな空行の混入で作業が止まらないよう対策しました。 key valueと1行に2つの文字列がない場合には無視して進みます。これは良いことか悪いことかは事情によります。

1行に3つ以上ある場合は、前の2つを採用して残りを無視します。これも良いことか悪いことかは事情によります。

str.format()を使ってみました。sがデフォルトになっていたり、右寄せ、左寄せのデフォルトが自然だったりとなかなか良さげです。

with open('onionw.txt') as f:
    lines = f.readlines()
oniondict = {}
for line in lines:
    #type(line.split())の戻値はlist。
    kandvlist = line.split()
    if len(kandvlist)>1:
        k = kandvlist[0]
        oniondict[k]=oniondict.get(k,0)+int(kandvlist[1])
for (k,v) in oniondict.items():
    # kandv はtuple
    print ('{:9}:{:4d}'.format(k,v))

実行結果

onion    : 345
carrot   :1673
radish   : 397
eggplant :  83

存在確認・削除などの操作の実際

命令文/関数でできていた昔風のものと、オブジェクト指向からくるメソッドが混在してスッキリしません。戻値を積極的にもたせない様です。

とにかく調査して一覧にしておきます。

fruits = {"apple":"りんご", "orange":"みかん", "peach":"もも"}
tmp=fruits.clear()
print('{}:{}'.format(type(tmp),tmp))    #NoneType:None
print(fruits) #{}
fruits = {"apple":"りんご", "orange":"みかん", "peach":"もも"}
tmp='apple' in fruits
print('{}:{}'.format(type(tmp),tmp))    #bool:True

tmp='apple' not in fruits
print('{}:{}'.format(type(tmp),tmp))    #bool:False
fruits['lemon']='レモン' # is not a method

tmp=fruits.setdefault('orange','オレンジ')
print('{}:{}'.format(type(tmp),tmp))    #str:みかん
tmp=fruits.setdefault('melon','メロン')
print('{}:{}'.format(type(tmp),tmp))    #str:メロン
#print(type(del fruits['peach'])) # is not a method
tmp=fruits.pop('peach','no peach') 
print('{}:{}'.format(type(tmp),tmp))    #strもも
tmp=fruits.pop('peach','no peach')
print('{}:{}'.format(type(tmp),tmp))    #str:No peach

#noexistdic.clear() #NameError: name 'noexistdic' is not defined
#len(noexistdic) #NameError: name 'noexistdic' is not defined
del fruits['lemon']
tmp='lemon' in fruits
print('{}:{}'.format(type(tmp),tmp))    #bool:False

実行結果

<class 'NoneType'>:None
{}
<class 'bool'>:True
<class 'bool'>:False
<class 'str'>:みかん
<class 'str'>:メロン
<class 'str'>:もも
<class 'str'>:no peach
<class 'bool'>:False

存在確認・削除などの操作の一覧

全部ではありませんが、調査したものを一覧にしておきます。

その他の操作(一部)
操作 使用例 対象が存在しない時 対象が存在する時
機能 動作 戻値 動作 戻値
clear dic.clear()
辞書の全ての項目を消去
NameError - 消去 -
len len(dic)
項目数を返す
NameError - - 項目数
in key in dic
dicにkeyが存在すればTrue。そうでなければFalse。
- False - True
not in key not in dic
dicがkeyが存在しなければTrue。存在すればFalse
- True - False
get dic.get(key[, default])
keyがあればvalue、なければdefaultを返す
- default - 現在のvalue
[] dic[key]='value'
keyがなければkey:valueを追加。あればvalueの更新
追加 - valueの更新 -
setdefault dic.setdefault(key[, default])
keyがあればvalueを返し、なければdefaultを返しkey:defaultを追加
追加 Default(None) - 現在のvalue
del del dic[key]
dic から keyと対応するvalueを削除
KeyError - 削除 -
pop dic.pop(key[, default])
keyが存在すればvalueを返してkey:valueを削除。しなければ default を返す
- default 削除 以前のvalue