pythonで連想配列やマップに相当するものは、辞書 (dictionary) です。ユニークな(重複できない)キーに対して値を対応させるものです。キーと値をkey,valueとしておきます。値という言葉を変数の値などで使用することがありますのでそれと区別するためです。
keyやvalueの型を予め決める必要はありません。項目ごとに混在させられます。マニュアルには「ハッシュ可能 (hashable) な値を任意のオブジェクトに対応付ける」とあります。
pythonにはmapという名前の関数がありますが、これはJavaなどのMapとは別物です。リストの各要素に指定した関数を適用する関数です。
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の方に様々な型の変数やオブジェクトを格納できることを示します。簡単にするため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の方に様々な型を使ってみます。実用では、文字列か数値(それも整数)を使うことになると思いますので、半分はお遊びの実験です。簡単にするため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
「ハッシュ可能 (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と同様な動きをします。ただ、文字列のタプルでは ' ' でないと同一と判断してもらえない様です。" " でも ' ' でも同じと聞いていたので意外です。
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でもファイルから一度に全部を読む方法がありますからファイルが大きくない場合はこれを使います。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
薄い色にした部分は、処理途中の確認のためです。改行や空白文字など動作確認が必要なものが多くありました。残しておくほうが参考になると考えました。
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 |