Python Toolzの使い方 Dicttoolz編

今回はToolzの中からDicttoolzを紹介します。Dicttoolzにあるメソッドはどれも使う場面が多そうだと思ったため、全て紹介します。

サンプルコードは全て”from toolz.dicttoolz import *”の様なインポート文を省略しています。

merge* dicts, ** kwargs 

辞書を結合します。同じキーがあった場合、後ろの辞書が優先されます。

merge({1: 2, 3: 4}, {3: 3, 4: 4})
print(result)

【結果】

{1: 2, 3: 3, 4: 4}

merge_withfunc, * dicts, ** kwargs 

辞書を結合し、結合された値に関数を適用します。

キーが重複した場合は、キーに紐づくすべての値が、func([val1、val2、…])のように関数に渡されます。

result = merge_with(sum, {1: 1, 2: 2}, {1: 10, 2: 20})
print(result)

【結果】

{1: 11, 2: 22}

valmap(func, d, factory=<type ‘dict’>)

辞書の値(イテラブル)に関数をマップします。

以下の例では値にsum関数をマップしています。

bills = {"けんじ": [20, 15, 30], "リサ": [10, 35]}
result = valmap(sum, bills)
print(result)

【結果】

{'けんじ': 65, 'リサ': 45}

辞書のキーに関数を適用する、keymap()という関数もあります。

itemmap(func, d, factory=<type ‘dict’>)

辞書のアイテム(キーと値)に関数を適用します。

以下の例ではキーと値を入れ替えています。

accountids = {"けんじ": 10, "リサ": 20}
result = itemmap(reversed, accountids)
print(result)

【結果】

{10: "けんじ", 20: "リサ"}

valfilter(predicate, d, factory=<type ‘dict’>)

辞書の値でアイテムをフィルターします。

以下の例では与えられた辞書から、値が偶数のアイテムの辞書を作成しています。

iseven = lambda x: x % 2 == 0
d = {1: 2, 2: 3, 3: 4, 4: 5}

result = valfilter(iseven, d)
print(result)

【結果】

{1: 2, 3: 4}

同様に辞書のキーでアイテムをフィルターするkeyfilter()という関数もあります。

itemfilter(predicate, d, factory=<type ‘dict’>)

辞書のアイテム(キーと値)でアイテムをフィルターします。

以下の例では与えられた辞書から、isvalid関数の戻り値がTrueとなるアイテムの辞書を作成しています。

def isvalid(item):
    k, v = item
    return k % 2 == 0 and v < 4

d = {1: 2, 2: 3, 3: 4, 4: 5}
result = itemfilter(isvalid, d)
print(result)

【結果】

{2: 3}

以下のように整理しておくと覚えてやすいです。

valmap, valfilter: 辞書の値に対して関数を適用する。

keymap, keyfilter:辞書のキーに対して関数を適用する。

itemmap, itemfilter:辞書のキーと値に対して関数を適用する。

assoc(d, key, value, factory=<type ‘dict’>)

assocとは”関連づける”、”連想する”という意味のassociateの略語です。

新しいキーと値のペアを代入した新しい辞書を返します。

新しい辞書の d[key]=value となるキーと値が設定されています。

引数に渡された辞書は変更しません。

関数型プログラミングでは参照透過性を失うため代入を行いません。

この関数を使うと与えられた辞書に変更を加えないで、新しいアイテムを適用した辞書を得られます。後に紹介するdissoc関数、assoc_in関数も同様です。

result = assoc({'x': 1}, 'x', 2)
result2 = assoc({'x': 1}, 'y', 3)
print(result)
print(result2)

【結果】

{'x': 2}
{'x': 1, 'y': 3}

assoc_in(dkeysvaluefactory=<type ‘dict’>)

ネストされた可能性のある新しいキーと値のペアを含む新しい辞書を返します。

purchase = {
    'name': 'ライラ',
    'order': {'items': ['りんご', 'なし'], 'costs': [0.50, 1.25]},
    'credit card': '5555-1234-1234-1234'
}

result = assoc_in(purchase, ['order', 'costs'], [0.25, 1.00])
print(result)

【結果】

{'name': 'ライラ', 'order': {'items': ['りんご', 'なし'], 'costs': [0.25, 1.0]}, 'credit card': '5555-1234-1234-1234'}

dissoc

指定したキーが削除された新しい辞書を返します。

キーは複数指定できます。元の辞書を変更しません。

result = dissoc({'x': 1, 'y': 2}, 'y')
result2 = dissoc({'x': 1, 'y': 2}, 'y', 'x')
result3 = dissoc({'x': 1}, 'y') # 存在しないキーを指定してもエラーにはなりません
print(result)
print(result2)
print(result3)

【結果】

{'x': 1}
{}
{'x': 1}

update_in(d, keys, func, default=None, factory=<type ‘dict’>)

ネストされている(と思われる)辞書の値を更新します。

【引数の説明】

  d – キーを操作する辞書

  keys – dで変更する値の場所を示すリストまたはタプル

  func – その値を操作する関数

if keys == [k0、..、kX] and d [k0] .. [kX] == v

を満たすの場合、update_in関数はvをfunc(v)に置き換えて元の辞書のコピーを返しますが、元の辞書は変更しません。 

inc = lambda x: x + 1
result = update_in({'a': 0}, ['a'], inc)
print(result)

transaction = {
    'name': 'ライラ',
    'purchase': {'items': ['りんご', 'なし'], 'costs': [0.50, 1.25]},
    'credit card': '5555-1234-1234-1234'
}
result2 = update_in(transaction, ['purchase', 'costs'], sum)
print(result2)

【結果】

{'a': 1}
{'name': 'ライラ', 'purchase': {'items': ['りんご', 'なし'], 'costs': 1.75}, 'credit card': '5555-1234-1234-1234'}

1つ目は、キーが’a’の値0にinc関数を適用し、1になった辞書が返されています。

2つ目は、キーが[‘purchase’][‘costs’]の値[0.50, 1.25]にsum関数を適用し、1.75になった辞書が返されています。

k0がdのキーではない場合、update_in関数はキーで指定された深さまでネストされた辞書を作成し、値をfunc(default)に設定します。

inc = lambda x: x + 1
result = update_in({}, [1, 2, 3], str, default="bar")
result2 = update_in({1: 'foo'}, [2, 3, 4], inc, 0)

print(result)
print(result2)

【結果】

{1: {2: {3: 'bar'}}}
{1: 'foo', 2: {3: {4: 1}}}

get_in(keys, coll, default=None, no_default=False)

[i0、i1、…、iX] == keysを満たす、coll [i0] [i1]…[iX]を返します。

もしcoll[i0] [i1]…[iX]が見つからない場合、defaultを返します。しかし、no_default=Trueに設定した場合は、KeyErrorまたはIndexErrorを発生させます。

get_in関数は、operator.getitemを辞書やリストなどのネストされたデータ構造のために一般化された関数です。

transaction = {
    'name': 'けん',
    'purchase': {'items': ['りんご', 'なし'],
    'costs': [0.50, 1.25]},
    'credit card': '5555-1234-1234-1234'
}

result = get_in(['purchase', 'items', 0], transaction)
print(result)

result = get_in(['name'], transaction)
print(result)

result = get_in(['purchase', 'total'], transaction)
print(result)

result = get_in(['purchase', 'items', 'りんご'], transaction)
print(result)

result = get_in(['purchase', 'items', 10], transaction)
print(result)

result = get_in(['purchase', 'total'], transaction, 0)
print(result)

【結果】

りんご
けん
None
None
None
0

    次の例は、no_default=Trueに設定した例です。KeyErrorが発生しています。

result = get_in(['y'], {}, no_default=True)
print(result)

【結果】

Traceback (most recent call last):
...
KeyError: 'y'

まとめ

リストに続き、辞書を処理する事は多いです。dicttoolzの機能は使い勝手が良いものが多いのでどんどん有効活用していきましょう。

itertoolzやfunctoolzと組み合わせると更に複雑なことでも簡潔に表現できます。

スポンサーリンク
スポンサーリンク