Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Of course it’s not the same. There is one sensible result expected when adding two lists or strings together (since they’re linear, it would not make sense to encourage inefficient search/replace operations with an operator; and also, the containers do not require unique keys, you can simply extend them). A dict must deal with conflicting keys, and arguably each situation requires different treatment of those keys.


Given the semantics of {}.update({}) and { * * {}, * * {}}, it is clear what is the preferred behavior, or at the very least the most commonly known one in Python.

Hence there is nothing hard or mysterious what the + operator should do. People are nit picking on this.


> Given the semantics of {}.update({}) and { * {}, * * {}}, it is clear what is the preferred behavior,*

Sorry, I honestly don't understand what's the meaning of that (I know a little bit of Python, C/C++, ASP.Net, etc...).

Concerning adding dicts: thinking over it I am more or less neutral as I think that somebody that would dare doing it would feel the pressure of reading the docs to know what the operator would do (e.g. merge distinct keys & overwrite-left/concatenate/ignore-right values of duplicate keys?).

On the other hand in Python I keep trying from time to time to e.g. cast "integer"s into "bytes" using "bytes(my_integer)", which results in a "my_integer"-bytes variable initialized to zeroes (ha-ha), so whatever would be implemented to add dicts using an op might be good for a large part of users but at the same time bad for the other part (that are inexperienced, have a different ways of thinking, etc...) => this might in turn weaken the language's acceptance.


They're the standard forms of doing dict merge in python, and generally the first answer whenever someone asks (both overwrite with right dict key/value on collision, concatenate keys otherwise).

{ * * d1, * * d2} -- unpack the two dicts and feed them both as input to dict comprehension to construct a new dict.

d1.update(d2) does the same merge strategy, but modifying d1 in-place.

Sure, there's five different ways to go about handling collisions, but there's already a well-defined, commonly used methodology, so it's fair to implement syntax sugar for it. It's also not too difficult to understand, and easy to look up (particularly compared to {* * d1,* * d2}).

And I'm not actually aware of any other merge strategy being included in the python stdlib, which implies that this strategy being the best useful default, has been decided long before this operator came into question.


The sensible thing is to throw on conflicting keys.


What? Have you never had two dicts and wanted vals from one dict override the other?


That’s semantically not an “add”, it’s a “merge” or “update”


What happens when you “merge” a pile of six pennies with a pile of three pennies? You get a pile of nine pennies. Six plus three is nine. Adding three pennies to six pennies results in a single group of nine pennies.

My point: don’t just pick words, explain why those words were chosen.


Penny piles can’t be merged together and remain pennies. If you merge a pile of 6 and a pile of 3, you get one metal lump. I actually really like the point you are making, just feel like your illustration works against you.


Problem with pennies as your example is it's hard to see how these work into a key value context.

If Penny is a class, then maybe you're asking what happens when you combine two arrays (or Python lists) of Penny objects? You get one array (list) containing all the Penny objects.

Perhaps you're getting at something like what if you have two "wallet" dictionaries you want to merge?

Let's say each wallet dict has a list of Penny objects, Dime objects, $5 notes, etc. If you are merging the wallets, maybe you don't want to overwrite the first wallet's Penny list with the second wallet's Penny list and instead you want to combine them.

This is where a dictionary comprehension can come in handy. Just iterate on the second wallet's items and add each item's value to the first wallet's corresponding value at the same key to create a new wallet object (or update one of your two existing wallets by setting the wallet equal to the comprehesion). You would have to add additional logic if you had any nested dictionaries in your wallet dictionary or another type that doesn't combine with the + operator, such as sets.

The other case is something like when your wallet gets sent off to the thief api. To make this more Pythonic, let's say you have a cached idea of your wallet's contents before your wallet itself is sent over the api. Once your actual wallet comes back and you pull it out to pay for something and realize it's empty, you merge the wallet you're holding with the cached idea of the wallet in your head, effectively updating the wallet to empty. If the key values are still on your returned wallet's dict, just now they're a bunch of empty lists as values, this will work fine:

    cached_wallet.update(my_wallet)
However, if the key value sets are removed entirely from the wallet returned by the thief api, you'd probably be better off doing:

    cached_wallet = my_wallet
This is because in Python the first example would have no affect on your cached idea of the wallet because an empty dict passed to the update method will not modify the dict you're attempting to update. So actually the second approach is much more robust here, unless of course it's problematic for some other part of your system to have a keyless wallet floating around (although I'd suggest fixing those other parts of your system by having defaults in place).

You could also use a dictionary comprehension in this case, making use of the get method with defaults while iterating on the emptied wallet you're holding like this maybe:

    cached_wallet.update({
        k: my_wallet.get(k, type(v)()) for k, v in cached_wallet.items()
    })
If the thief put a new key value pair in your wallet, you wouldn't get it in the above comprehension, so you might want to do this in some cases:

    cached_wallet.update(
        **{ k: my_wallet.get(k, type(v)()) for k, v in cached_wallet.items() },
        **my_wallet,
    )
Although that may be called out as more expensive/redundant than necessary, you may want to combine the .keys() from both dictionaries, cast them as a set to dedupe then iterate through the actual wallet on those and fuck all, you're tossing the dirty tissue he stuffed into your wallet in the garbage anyway ... but then actually you decide to keep it, there may be DNA evidence here ... you have no idea how you're going to actually parse and apply this evidence and it's kinda disgusting and, ugh, get a hold of yourself, toss that out and go wash your hands and then focus on making sure the credit cards that are missing are canceled and hope to God that updating your card number on your Fubo TV streaming account doesn't invalidate your legacy subscription that lets you watch the Barcelona game each weekend for $10/mo because there's no way in hell you're going to start paying them $40/mo, that's bullshit.


I mean, that’s fine, my only point it is not obvious. And you are turning + into a partial function, where it is not usually


If a+b != b+a that breaks the commutativity property of addition.


Yes, but I still want this for combining dictionaries. Python already violates this property, I don't find it confusing.

    Python 3.7.2 (default, Dec 30 2018, 08:55:50) 
    [Clang 10.0.0 (clang-1000.11.45.5)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> 'foo' + 'bar' == 'bar' + 'foo'
    False
    >>> ['foo'] + ['bar'] == ['bar'] + ['foo']
    False


"a"+"b" != "b"+"a" already.


That property is not universal, and it is currently being defined for Python dictionaries. You can't break a property that isn't defined yet. ;-)


Is this what changed in 3.5?


Commutatitivity does not need to always hold. Multiplication is not commutative on matrices, but we still use it often and write it as a product nonetheless


In mathematics, the ‘multiplication’ operation is not assumed to be commutative, whereas the ‘addition’ operator is used specifically to indicate commutativity. So, for example, using the plus sign for string concatenation goes against this tradition.


Python actually supports a special operator @ for non-commutative matrix products.


To be fair, IEEE fp addition is noncommutative in corner cases.


No. That's the absolutely worse way of solving the problem.

1) I either want them to be overridden or not. The current .update can do both, you just put the dict you don't want to override as its argument

2) finding common keys is a different problem, which can be solved in a different way

So no, throwing an exception is just completely useless and won't make any of the common cases easier


The even more sensible thing is to take an optional argument: a function that is passed the conflicting keys and their values, and must resolve the conflict. If `None` is passed, then a documented default is taken.


It’s an operator, there are no optional arguments. Though it’s OK to also have a function like you propose.


Recursively calling the operator is an option.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: