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


139

私はやろうとしている:

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で受け入れられないのですか?


14
* TL; DRnewdict = dict(dict001, **dict002)
dreftymac

2
@dreftymac、それは内包表記では機能しません。
alancalvitti

@alancalvittiはい、それは確かに指摘すべき有効な警告の1つです。
dreftymac

回答:


219

Pythonは、ほとんどの場合、実用的な色合いのコマンドとクエリの分離を実装しています。ミューテーターはNone(実用的な方法で引き起こされる; -などの例外を使用して)戻るpopため、アクセサーと混同される可能性はありません(割り当ては式ではなく、ステートメントです)式の分離がある、など)。

それはあなたが本当に欲しいときに物事をマージするための多くの方法がないことを意味するわけではありません。たとえば、dict(a, **award_dict)あなたが.update返すように思われるものに似た新しい辞書を作る-それでそれが本当に重要だと思うならそれを使用しないでください?

編集:ところで、あなたの特定のケースでaは、途中で作成する必要はありません:

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

とまったく同じセマンティクスで1つの辞書を作成しますa.update(award_dict)(競合の場合、エントリがaward_dict明示的に指定したものをオーバーライドするという事実を含みます。他のセマンティクスを取得するために、つまり、そのような競合に「勝つ」明示的なエントリを持つために、合格award_dict唯一のような位置、引数の前にキーワードのもの、との失っ**形- dict(award_dict, name=nameなどなど)。


まあ、それは私が作った後に別の辞書を作成します。私は辞書を作成し、他の値の束を追加して、それを関数に与えたかったのです。
ポールタージャン

@Paul、それはまさにあなたがやっていることです-「本当に面倒だと感じた」2つのステートメント(望んだ入れ子の方法よりもはるかに読みやすい)です。私の回答を編集して、aまったく作成しないようにする方法を示します
Alex Martelli

1
元のソリューションは堅牢ではありません。award_dictにすでに指定されているキーが含まれている場合、繰り返されるキーワード引数に対してSyntaxErrorがスローされます。jamylakのソリューションdict(itertools.chain(d1.iteritems()、.. d <n> .iteritems()))は、辞書に重複したキーがある場合に機能するだけでなく、複数の辞書を後でdictにマージすることも簡単にできます最終的な値に優先するチェーン。
マット

2
award_dictのキーが文字列でない場合にも、通訳がスローされますTypeError
kunl

3
dict(old_dict, old_key=new_value)キーワードに複数の値をスローせず、新しい辞書を返します。
チャーミー、

35

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

そうすることの動機はそうでなければ、あなたは望ましくない副作用を得るかもしれないということです。検討する

bar = foo.reverse()

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


1
ありがとうございました。逆に、インプレースで実行しないオプションも提供しないのはなぜですか?パフォーマンス?することreverse(foo)は奇妙に感じます。
ポールターヤン

オプションを追加するのは不適切です。パラメーターによっては、メソッドの性質が変化します。ただし、メソッドには実際には固定の戻り値の型が必要です(残念ながら、このルールに違反する場合があります)。bar=foo[:]元に戻すコピーは簡単に作成できます。コピーを(を使用して)作成してから、元に戻します。
Martin v。Löwis09年

3
その理由は明白だと思います。ではbar = foo.reverse()foo変更されていないと考えることができます。混乱を避けるために、との両方foo.reverse()を使用しbar = reversed(foo)ます。
Roberto Bonvallet

パラメータに基づいてパラメータの性質を変更することの何が問題になっていますか?
ジュリアン


15
>>> 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を返すだけでなく、最初のパラメーターを変更します。したがって、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}

10
-1 lambdaはそのように使用しないでください。代わりに従来の関数をdef使用してください
jamylak

8
ラムダも必要ありません、使用してくださいa.update(b) or a
Pycz

10

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

@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}

5

それは受け入れられないということではなく、むしろ dictsその方法で実装されなかったということです。

DjangoのORMを見ると、チェーンが広範囲に使用されています。推奨されていません。本当に必要な場合は、更新とを継承しdictてオーバーライドすることさえできます。updatereturn self

class myDict(dict):
    def update(self, *args):
        dict.update(self, *args)
        return self

おかげで、これはdictにパッチを当てることができました、私はただdict()がこの機能自体を許可しなかった理由を知りたかっただけです(あなたが示すのと同じくらい簡単なので)。Djangoパッチはこのように言いますか?
ポールタージャン

2

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

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

1

パーティーに遅れて来る人のために、私はいくつかのタイミングをまとめました(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
import itertools
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args]))

0

Python 3.4でこれを自分で試してみました(そのため、派手な{**dict_1, **dict_2}構文を使用できませんでした)。

辞書に文字列以外のキーを設定し、任意の数の辞書を提供できるようにしたいと考えていました。

また、私は新しい辞書を作りたかったので、使用しないことを選びましたcollections.ChainMapdict.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}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.