web-dev-qa-db-ja.com

python dict.update()がオブジェクトを返さないのはなぜですか?

私がやろうとしている:

award_dict = {
    "url" : "http://facebook.com",
    "imageurl" : "http://farm4.static.flickr.com/3431/3939267074_feb9eb19b1_o.png",
    "count" : 1,
}

def award(name, count, points, desc_string, my_size, parent) :
    if my_size > count :
        a = {
            "name" : name,
            "description" : desc_string % count,
            "points" : points,
            "parent_award" : parent,
        }
        a.update(award_dict)
        return self.add_award(a, siteAlias, alias).award

しかし、機能が本当に面倒だと感じたら、私はむしろやったでしょう:

        return self.add_award({
            "name" : name,
            "description" : desc_string % count,
            "points" : points,
            "parent_award" : parent,
        }.update(award_dict), siteAlias, alias).award

更新してもオブジェクトが返されないので、チェーンできるのはなぜですか?

JQueryはこれを行って連鎖を行います。なぜPythonでは受け入れられないのですか?

115
Paul Tarjan

Pythonは、 コマンドとクエリの分離 の実用的な色合いのフレーバーを主に実装しています。ミューテーターは、Noneを返します(pop ;-)アクセサーと混同される可能性はありません(同じように、代入は式ではなく、ステートメントと式の分離があります)。

つまり、本当に欲しいときに物事をマージする方法があまりないというわけではありません。たとえば、dict(a, **award_dict).updateが返されることを望むように見えるものと同じような新しい辞書を作成します。本当にそれが重要だと感じたら?

Edit:ところで、特定の場合、途中でaを作成する必要はありません。

dict(name=name, description=desc % count, points=points, parent_award=parent,
     **award_dict)

a.update(award_dict)とまったく同じセマンティクスで単一の辞書を作成します(競合の場合、award_dictのエントリが明示的に与えているものをオーバーライドするという事実を含みます;他のセマンティクスを取得する、つまり明示的にするそのような競合に「勝つ」エントリは、award_dictを唯一のpositional arg、beforeキーワードのもの、**形式のbereft --dict(award_dict, name=nameなど)として渡します。

188
Alex Martelli

PythonのAPIは、慣例により、プロシージャと関数を区別します。関数は、パラメーター(ターゲットオブジェクトを含む)から新しい値を計算します。プロシージャはオブジェクトを変更し、何も返しません(つまり、Noneを返します)。したがって、プロシージャには副作用がありますが、関数にはありません。更新はプロシージャであるため、値を返しません。

そうしないと、望ましくない副作用が発生する可能性があります。検討する

bar = foo.reverse()

Reverse(インプレースでリストを逆にする)がリストを返す場合、ユーザーはreverseがbarに割り当てられた新しいリストを返し、fooも変更されることに気付かないかもしれません。リバースリターンをNoneにすると、バーはリバースの結果ではないことをすぐに認識し、リバースの効果が何であるかをより近くに見えます。

33
>>> dict_merge = lambda a,b: a.update(b) or a
>>> dict_merge({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}

マージされた辞書を返すだけでなく、最初のパラメーターをその場で変更することに注意してください。したがって、dict_merge(a、b)はaを変更します。

または、もちろん、すべてインラインで実行できます。

>>> (lambda a,b: a.update(b) or a)({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}
13

これは簡単です:

(lambda d: d.update(dict2) or d)(d1)
8

トップアンサーに残されたコメントの評判が不十分です

@beardcこれはCPythonのものではないようです。 PyPyから「TypeError:キーワードは文字列でなければなりません」と表示されます

**kwargsを使用したソリューションは、マージされるディクショナリがstring型のキーのみを持つためにのみ機能します。

つまり.

>>> dict({1:2}, **{3:4})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

>>> dict({1:2}, **{'3':4})
{1: 2, '3': 4}
8

受け入れられないということではなく、dictsがそのように実装されていないということです。

DjangoのORMを見ると、連鎖を広く利用しています。非推奨ではありません。dictから継承し、updateのみをオーバーライドして更新とreturn self、本当に必要な場合。

class myDict(dict):
    def update(self, *args):
        dict.update(self, *args)
        return self
5
Esteban Küber

私が得ることができる限りあなたの提案されたソリューションに近い

from collections import ChainMap

return self.add_award(ChainMap(award_dict, {
    "name" : name,
    "description" : desc_string % count,
    "points" : points,
    "parent_award" : parent,
}), siteAlias, alias).award
2
Matus

パーティーに遅れて来る人のために、私はいくつかのタイミングをまとめました(Py 3.7)。入力が保存されると.update()ベースのメソッドが少し(〜5%)速く、そして顕著に(〜30%)見えることを示しましたインプレース更新するだけで高速になります。

いつものように、すべてのベンチマークは一粒の塩で取得する必要があります。

def join2(dict1, dict2, inplace=False):
    result = dict1 if inplace else dict1.copy()
    result.update(dict2)
    return result


def join(*items):
    iter_items = iter(items)
    result = next(iter_items).copy()
    for item in iter_items:
        result.update(item)
    return result


def update_or(dict1, dict2):
    return dict1.update(dict2) or dict1


d1 = {i: str(i) for i in range(1000000)}
d2 = {str(i): i for i in range(1000000)}

%timeit join2(d1, d2)
# 258 ms ± 1.47 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit join(d1, d2)
# 262 ms ± 2.97 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit dict(d1, **d2)
# 267 ms ± 2.74 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit {**d1, **d2}
# 267 ms ± 1.84 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

インプレース操作のタイミングは少し複雑なので、追加のコピー操作に合わせて変更する必要があります(最初のタイミングは参照用です)。

%timeit dd = d1.copy()
# 44.9 ms ± 495 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit dd = d1.copy(); join2(dd, d2)
# 296 ms ± 2.05 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit dd = d1.copy(); join2(dd, d2, True)
# 234 ms ± 1.02 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit dd = d1.copy(); update_or(dd, d2)
# 235 ms ± 1.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
0
norok2

Python 3.4でこれを自分で試してみました(したがって、ファンシーな{**dict_1, **dict_2}構文を使用できませんでした)。

辞書に文字列以外のキーを持ち、任意の量の辞書を提供できるようにしたかったのです。

また、新しい辞書を作成したかったので、collections.ChainMapを使用しないことにしました(最初はdict.updateを使用したくなかった理由です)。

これが私が書いたものです:

def merge_dicts(*dicts):
    all_keys  = set(k for d in dicts for k in d.keys())
    chain_map = ChainMap(*reversed(dicts))
    return {k: chain_map[k] for k in all_keys}

merge_maps({'1': 1}, {'2': 2, '3': 3}, {'1': 4, '3': 5})
# {'1': 4, '3': 5, '2': 2}
0
freebie
import itertools
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args]))
0
Matt