Reputation: 50662
I have this code:
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
But the code felt rather cumbersome. I would have preferred to be able to write:
def award(name, count, points, desc_string, my_size, parent):
if my_size > count:
return self.add_award({
"name": name,
"description": desc_string % count,
"points": points,
"parent_award": parent,
}.update(award_dict), siteAlias, alias).award
Why doesn't the update
method return
the original dictionary, so as to allow chaining, like how it works in JQuery? Why isn't it acceptable in python?
See How do I merge two dictionaries in a single expression in Python? for workarounds.
Upvotes: 209
Views: 107461
Reputation: 643
not enough reputation for comment left on top answer
@beardc this doesn't seem to be CPython thing. PyPy gives me "TypeError: keywords must be strings"
The solution with **kwargs
only works because the dictionary to be merged only has keys of type string.
i.e.
>>> dict({1:2}, **{3:4})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings
vs
>>> dict({1:2}, **{'3':4})
{1: 2, '3': 4}
Edit: Indeed this is a clever solution, but not a good solution! It is not, as pointed out by others, because one won't be able to merge a dictionary with int keys using the ** unpacking operator, see use dict as kwargs.
Here, the ** unpacking operator for the {3: 4}
dictionary is used to provide named arguments (and fails because keyword arguments must be strings). To clarify, this answer was meant as a comment and an illustration that the error isn't specific to CPython, but not as solution. As a solution consider whether you need to keep both values when merging a key present in both dictionaries or if the python 3.9 | operator is an option and find a different answer.
Upvotes: 31
Reputation: 181
Merge by concatenating list of items or dict union:
d1 = {1: "one"}
d2 = {2: "two"}
dict(list(d1.items()) + list(d2.items()))
# {1: 'one', 2: 'two'}
d1 | d2
# {1: 'one', 2: 'two'}
Upvotes: 2
Reputation: 2878
Nowadays, with Python 3.9, you can use the |
operator, see:
https://docs.python.org/3.9/whatsnew/3.9.html#dictionary-merge-update-operators
Quoting from there:
>>> x = {"key1": "value1 from x", "key2": "value2 from x"}
>>> y = {"key2": "value2 from y", "key3": "value3 from y"}
>>> x | y
{'key1': 'value1 from x', 'key2': 'value2 from y', 'key3': 'value3 from y'}
>>> y | x
{'key2': 'value2 from x', 'key3': 'value3 from y', 'key1': 'value1 from x'}
Upvotes: 6
Reputation: 455
This is easy as:
(lambda d: d.update(dict2) or d)(d1)
Or, if it is important not to modify the dictionary:
(lambda d: d.update(dict2) or d)(d1.copy())
Upvotes: 35
Reputation: 26946
For those coming late to the party, I had put some timing together (Py 3.7), showing that .update()
based methods look a bit (~5%) faster when inputs are preserved and noticeably (~30%) faster when just updating in-place.
As usual, all the benchmarks should be taken with a grain of salt.
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)
The timings for the in-place operations are a bit trickier, so it would need to be modified along an extra copy operation (the first timing is just for reference):
%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)
Upvotes: 3
Reputation: 1957
Just been trying this myself in Python 3.4 (so wasn't able to use the fancy {**dict_1, **dict_2}
syntax).
I wanted to be able to have non-string keys in dictionaries as well as provide an arbitrary amount of dictionaries.
Also, I wanted to make a new dictionary so I opted to not use collections.ChainMap
(kinda the reason I didn't want to use dict.update
initially.
Here's what I ended up writing:
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}
Upvotes: 2
Reputation: 613
as close to your proposed solution as I could get
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
Upvotes: 2
Reputation: 1901
import itertools
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args]))
Upvotes: 2
Reputation: 127547
Python's API, by convention, distinguishes between procedures and functions. Functions compute new values out of their parameters (including any target object); procedures modify objects and don't return anything (i.e. they return None). So procedures have side effects, functions don't. update is a procedure, hence it doesn't return a value.
The motivation for doing it that way is that otherwise, you may get undesirable side effects. Consider
bar = foo.reverse()
If reverse (which reverses the list in-place) would also return the list, users may think that reverse returns a new list which gets assigned to bar, and never notice that foo also gets modified. By making reverse return None, they immediately recognize that bar is not the result of the reversal, and will look more close what the effect of reverse is.
Upvotes: 47
Reputation: 223
>>> dict_merge = lambda a,b: a.update(b) or a
>>> dict_merge({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}
Note that as well as returning the merged dict, it modifies the first parameter in-place. So dict_merge(a,b) will modify a.
Or, of course, you can do it all inline:
>>> (lambda a,b: a.update(b) or a)({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}
Upvotes: 19
Reputation: 882671
Python's mostly implementing a pragmatically tinged flavor of command-query separation: mutators return None
(with pragmatically induced exceptions such as pop
;-) so they can't possibly be confused with accessors (and in the same vein, assignment is not an expression, the statement-expression separation is there, and so forth).
That doesn't mean there aren't a lot of ways to merge things up when you really want, e.g., dict(a, **award_dict)
makes a new dict much like the one you appear to wish .update
returned -- so why not use THAT if you really feel it's important?
Edit: btw, no need, in your specific case, to create a
along the way, either:
dict(name=name, description=desc % count, points=points, parent_award=parent,
**award_dict)
creates a single dict with exactly the same semantics as your a.update(award_dict)
(including, in case of conflicts, the fact that entries in award_dict
override those you're giving explicitly; to get the other semantics, i.e., to have explicit entries "winning" such conflicts, pass award_dict
as the sole positional arg, before the keyword ones, and bereft of the **
form -- dict(award_dict, name=name
etc etc).
Upvotes: 287
Reputation: 36862
Its not that it isn't acceptable, but rather that dicts
weren't implemented that way.
If you look at Django's ORM, it makes extensive use of chaining. Its not discouraged, you could even inherit from dict
and only override update
to do update and return self
, if you really want it.
class myDict(dict):
def update(self, *args):
dict.update(self, *args)
return self
Upvotes: 6