{ 'a' : 1 } | { 'a' : 2 }
Should the result be: { 'a' : 1 }
(prioritise the left hand side), or { 'a' : 2 }
(prioritise the right hand side), or should it raise an error? Maybe a fourth option would be do downgrade to sets of keys and give: { 'a' }
A fifth option is to magically merge values: { 'a' : 3 } or { 'a' : (1,2) }
For the first two choices one loses commutativity which means that code then suddenly has to have previously cared about it (or it will do the wrong thing), even though it didn’t previously matter, and one is always potentially losing data. The third choice is safe but could cause unforeseen problems later if shared keys only happen rarely. The fourth choice also forgets a bunch of information held in the dict.In a language like Haskell, one can use traits to specify how to merge values (Monoid) but without traits (and a way to choose which trait to use) I think some kind of magic merge is not great.
I claim the operations one should really want with dicts are not set operations but rather more relational ones, ie {inner,outer,left,right} joins on the keys followed by some mapping to decide how to merge values.
Dumb program to illustrate this point:
class Dummy:
def __init__(self, value): self.value = value
def __repr__(self): return 'Dummy(%s)' % self.value
def __hash__(self): return 0
def __eq__(self, other): return True
a = {Dummy(0)}
b = {Dummy(1)}
print(a | b)
print(b | a)
print(a | b == b | a)The comparison was to say, "this decision is difficult everywhere" -- which lang seems beside the point.
d1 | d2 | d3 | ...
is equivalent to: {**d1, **d2, **d3, ...} d1 | d2
should be because losing information/desirable properties/weird errors”, read “it’s not super obvious what {**d1, **d2}
should be because losing information/desirable properties/weird errors”.Except I guess one could throw in something about TOOWTDI too.
{**d1, **d2}
In other words, there are no new semantics to discuss here. I'm just saying the two syntaxes should be equivalent.Since this is a new operator, that shouldn’t be an issue.
I think losing commutivity is okay. After all, d1.update(d2) != d2.update(d1) if keys conflict.
Secondly I claim that the issue with using | is that it is not a new operator. It is a new, incompatible meaning for an old operator. Old code might not bother checking that its arg is a set because of it weren’t a set then | or in would fail. New programmers might see dicts as being basically sets and wrongly assume functions for sets would correctly work on dicts.
d1 = {'a': 1}
d2 = {'a': 2}
d3 = {**d1, **d2, add_func)
def add_func(a, b):
return a+b
Or something along those linesI assume the same key with the same value would be OK, but I'm not really sure it's a good idea for it to be OK.
a & b = { k: (a[k], b[k]) for k in a.keys() | b.keys() }
a | b = { k: (a.get(k, None), b.get(k, None)) for k in a.keys() | b.keys() }
a |& b = { k: (a.get(k,None), v) for k, v in b.items() }
a &| b = { k: (v, b.get(k,None)) for k, v in a.items() }
a |_| b = { k: only(a,b,k) for k in a.keys() | b.keys() }
def only(a,b,k):
if k in a && k in b:
throw DuplicateKey(a,b,k)
elseif k not in a && k not in b:
assert(false)
elseif k in a:
return a[k]
else:
return b[k]
This doesn’t work well if values can be None so maybe instead of pairs there should be objects Left(x), Right(y), and Both(x,y) {**d1, **d2}
idiom is just a clever mashup of Python's dictionary construction literal {}, and * * unpacking. That's why it only works with string-valued keys (which is a major limitation).Adding a third item to the dictionary literal would require special-casing the {} dictionary construction literal.
>>> { 'a' : 1 } | { 'a' : 2 }
ISTM the most logical result would be: { 'a' : { 1, 2 } }
...but I could certainly understand throwing an exception.I'm not saying you have a bad idea/logic here, just that I'm not sure it's the best thing for the dict.
Such a derivation would violate the Liskov substitution principle. Consider the following with set:
x = {"one", "two"}
y = set()
y.update(x)
y
It result in y being {'two', 'one'} .Now, do the same with dict:
y = dict()
y.update(x)
This gives the exception: "ValueError: dictionary update sequence element #0 has length 3; 2 is required"This means that dict cannot be used anywhere that a set can be used, which means it violates the Liskov substitution principle (see https://en.wikipedia.org/wiki/Liskov_substitution_principle ) which means that if covariant methods are needed for good design then dict cannot be a subclass of set.
>>> s = {'one', 'two'}
>>> d = {}
>>> d.update(s)
>>> d
{'two': None, 'one': None}None seem like a good design since it means either the instance change its class on the fly (which Python does support) or that a dict does not act like its parent set object, breaking the is-a relationship most people expect from an OO design.
It seems like the circle/ellipse problem, and the current implementation is the "drop all inheritance relationships" solution to that problem. https://en.wikipedia.org/wiki/Circle-ellipse_problem#Drop_al...
Sets don't support indexing, so it would still raise an exception. Dicts do, which is an example of them supporting more operations than sets, which is an example of why (if there is to be any subclass relation) dicts are subclasses of sets.
Edit: I suppose there's some confusion about my language above. "then sets would be dicts whose values are all None" could more helpfully read "sets would be equivalent to dicts whose...".
Interestingly enough some languages actually do the opposite. In Rust for example a set is literally just a dictionary with unit as the value[0] and unit is essentially a way of expressing the absence of a value (it takes up no space in memory, and you just can't do anything with it).
[0]: https://doc.rust-lang.org/stable/src/std/collections/hash/se...
For posterity, the above link shows:
pub struct HashSet<T, S = RandomState> {
map: HashMap<T, (), S>,
}FWIW, I'm usually using map[T]bool and only ever inserting `true` values. It uses a bit more space, but membership checks read like
if set[key] {
instead of if _, ok := set[key]; ok {Of course you can represent sets as dictionaries with empty values (ask anyone who programmed Perl). You're supporting my point that dicts logically subclass sets, because they can represent sets where the values can be other things as well.
You're also getting at what the behavior should be if you union a dictionary and a set. Hypothetical Python:
>>> s = {'a','b','c'}
>>> d = {'d': 'D'}
>>> d | s
>>> {'d': 'D', 'a': None, 'b': None, 'c': None}> There's no reason you shouldn't have always been able to say d | d2, same as sets.
I don't agree with this view, mainly because merging a dict is not associative, while unionizing a set is.
The actual operation for "a + b" is "add everything from b set to a", and + more closely resembles that than |.
>>> a = {"foo": 1}
>>> b = {"bar"}
>>> a.keys() | b
{'bar', 'foo'}
As an aside, I like the plus operator. Begin able to merge two dictionaries in one line and have the result be a new dict is something I've needed often enough. {**d, **d2}
works, but is pretty recent and still feels weird to me (not coming from a language that makes use of destructuring a lot, like Javascript).You're illustrating my point:
>>> set(a) | b
{'bar', 'foo'}And I'd be sort of okay if dicts implemented set operators, although I don't think using `set()` or `.keys()` is a big ask. But using the pipe operator for the operation in this PEP would be a bad idea IMO: For sets, a | b == b | a. For dicts, not necessarily. So if they used the pipe operator, that could lead to surprising or unintuitive results.
On the plus side, not using pipe still leaves it open for future usage, so you might get your wish yet.
I think a variant of https://en.wikipedia.org/wiki/Composition_over_inheritance applies here. Inheritance, in general, only is a good idea if there is a strong isa relation. A dictionary isn’t a set of keys, it has a set of keys.
If you want to see a dictionary as a set, I think the better view would be to see it as a set of (key,value) pairs where equality of pairs is defined as equality of the key parts, ignoring the ‘value’ parts.
I think it makes sense to require that such a set should behave identical to a dictionary, and that providing a ‘real’ dictionary is just an optimization, plus the addition of convenience functions, e.g. to get the set of keys.
If one sees things that way, one could even define the dictionary interface as taking an equality operation on the keys and a ‘value combiner’ function that combines values, and will be used in the cases you outline (that function could add integers, concatenate strings, keep the larger value, or whatever the programmer specifies)
He says that sets are like dictionaries without values, e.g. a set is akin to the dictionary keys.
So, in your example the ('a', 1) and ('a', 2) are the keys (in how the parent argues about) -- it's not 'a' that's the key.
Same like you can do today:
d = {}
d[('a', 1)] = 6
d[('a', 2)] = 89
We can still express that dictionary as a set of tuples, it's just: {(('a', 1), 6), (('a', 2), 89)}> dicts would subclass sets
Was trying to make the case as to why treating dictionaries as a child of <generic collection> with an extend/merge operation using ‘+’ rather than a child of set with a union operation ‘|’ makes more sense (to me).
Changing the behaviour of a well defined operation like union seems bad - although my case is somewhat undermined by python’s overloading of ‘+’ to mean extend.
Most Python coders don't even use sets more than once a year. Hell, I use collections.deque more than sets.
But dicts ? We use it all the time. In fact, failing a {} + {} is a recurring disapointment in all my classrooms.
Plus, in PHP and JS, arrays/objects are the "do-it-all" data structure. And it's horrible. You see the same data structure everywhere. You have to read in details what something is, and what it's for.
It very nice that dict and set are very distincts, and that they have a distint set of operators. This way it's super easy to scan the code and know what the data structure is, and what it's used for. That's why I always teach set([1, 2]) and not {1, 2} first. It helps people to make a clear distinction in their mind.
Size sample: a few hundred students and colleagues over 15 years.
It make sense: for columns, it's quite commonto use dicts, or pandas dataframe. The sets of key is just the dict keys memory view.
A web dev rarely needs sets. A GUI coders, a sysadmin or a geograph neither.
It's not that sets are not useful, it's just that in the huge numbers of things you need to do in programming, accross all fields that Python attends to, they are pretty niche.
This is exactly why I want explicit typing in Python.
But type declaration may not be in the view port.
Or you may read a script or snippet, which is a type or code that won't bother with typing.
Type declaration doesn't have to be in the view; the very point of type annotations is to support static type systems, which you can query about any type. For example, if you use mypy, you can add `reveal_type( any_kind_of_expr )` anywhere in your code and you'll get the most precise type available, be it inferred or declared manually, for that expression at given point in code.
That's disappointing. It's always been on my Python wish list that dicts would subclass sets, as dicts are essentially sets with values attached. Pretty much everywhere you can use a set you can use a dict and it acts like the set of its keys. For example:
Dictionaries have been moving in this more ergonomic direction for a while. Originally, to union two dictionaries you had to say: Nowadays, as the PEP points out, you can just say: There's no reason you shouldn't have always been able to say d | d2, same as sets. Now I finally get my wish that dictionaries will behave more similarly to sets and they use the wrong set of operators.