What's going on here?
>>> a = {1: "a", 2: "b"}
>>> del a[1]
>>> a
{2: 'b'}
>>> a = {1: "a", 2: "b"}
>>> del a[:]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type
>>> a.clear()
>>> a
{}
Why must I call dict.clear?
a[:] is a special case of a slicing operation, which is defined only for sequences. It is a short form of a[0:len(a)]. A dict is not a sequence.
a[:] is a quick way to make a shallow copy of a list (and tuple). See towards the bottom of the docs for clarification on different types of copying.
Thus, it would reason to say that del a[:] on a dictionary doesn't really make much sense.
If you want to delete the entire dictionary, you can simply do del a
>>> a = {1: "a", 2: "b"}
>>> del a
>>> a
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
a
NameError: name 'a' is not defined
>>>
The reason is rather simple: It isn't defined. There's no reason why it couldn't work, if someone put the effort into catching the empty slice and delegating to the appropriate method.
On the other hand, this would violate a symmetry principle with assigning and getting the same slice, so probably would not gain acceptance.
If this functionality is crucial to your system, you could subclass the dict builtin to add this function.
import sys
class newdict(dict):
def __delslice__(self,i,j):
if i==0 and j==sys.maxint:
self.clear()
else:
dict.__delslice__(self,i,j)
When you're doing del a[1] you're not deleting the first element in dictionary, you're deleting the element with key 1. Dictionary does not have any ordering and thus slicing algorigthm (in this case at least).
That's why with the dict a = {1 : 'one', 5 : 'five'} you can do del a[1] and cannot do del a[2] or del a[0]
Related
There are dozens of questions on turning a Python dict with some number of elements into a list of tuples. I am looking for a shortcut to turn a one element dict into a tuple.
Ideally, it would:
Be idiomatic and simple (ie, not a subclass of dict, not a function, etc).
Throw an error if there are more that one element in the dict.
Support multiple assignment by tuple unpacking.
Not destroy the dict.
I am looking for a shortcut to do this (that is not destructive to the dict):
k,v=unpack_this({'unknown_key':'some value'})
* does not work here.
I have come up with these that work:
k,v=next(iter(di.items())) # have to call 'iter' since 'dict_items' is not
Or:
k,v=(next(((k,v) for k,v in di.items())))
Or even:
k,v=next(zip(di.keys(), di.values()))
Finally, the best I can come up with:
k,v=list(di.items())[0] # probably the best...
Which can be wrapped into a function if I want a length check:
def f(di):
if (len(di)==1): return list(di.items())[0]
raise ValueError(f'Too many items to unpack. Expected 2, got {len(di)*2}')
These methods seem super clumsy and none throw an error if there is more than one element.
Is there an idiomatic shortcut that I am missing?
Why not :
next(iter(d.items())) if len(d)==1 else (None,None)
>>> d = {'a': 1}
>>> d.popitem()
('a', 1)
This will return the pair associated with the last key added, with no error error if the dict has multiple keys, but the same length check you've made in your function f can be used.
def f(di):
if len(di) == 1:
return d.popitem()
raise ValueError(f'Dict has multiple keys')
#chepner has the right approach with .popitem():
>>> d = {'a': 1}
>>> d.popitem()
('a', 1) # d is now {}
This will return the pair associated with the last key added, with no error error if the dict has multiple keys. It will also destroy d.
You can keep d intact by using this idiom to create a new dict and pop off the last item from the new dict created with {**d}:
>>> {**d}.popitem()
('a', 1)
>>> d
{'a': 1}
As far as a 1 line unpackable tuple that throws an error if the dict has more than one item, you can use a Python conditional expression with an alternate that throws the error desired:
# success: k=='a' and v==1
>>> d={'a':1}
>>> k,v={**d}.popitem() if len(d)==1 else 'wrong number of values'
# len(d)!=1 - pass a string to k,v which is too long
>>> d={'a':1,'b':2}
>>> k,v={**d}.popitem() if len(d)==1 else 'wrong number of values'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)
If you object to different types being passed, you could do:
>>> k,v={**d}.popitem() if len(d)==1 else {}.fromkeys('too many keys').items()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)
I have face this weird behavior I can not find explications about.
MWE:
l = [1]
l += {'a': 2}
l
[1, 'a']
l + {'B': 3}
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: can only concatenate list (not "dict") to list
Basically, when I += python does not raise an error and append the key to the list while when I only compute the + I get the expected TypeError.
Note: this is Python 3.6.10
l += ... is actually calling object.__iadd__(self, other) and modifies the object in-place when l is mutable
The reason (as #DeepSpace explains in his comment) is that when you do l += {'a': 2} the operation updates l in place only and only if l is mutable. On the other hand, the operation l + {'a': 2} is not done in place resulting into list + dictionary -> TypeError.
(see here)
l = [1]
l = l.__iadd__({'a': 2})
l
#[1, 'a']
is not the same as + that calls object.__add__(self, other)
l + {'B': 3}
TypeError: can only concatenate list (not "dict") to list
So as the authors say this is not a bug. When you Do a += b it is like b come to a's house and changing it the way that a like it to be. what the Authors say is when you do a + b it cannot be decided which one's style will get prioritized. and no one knows where will the result of a + b will go until you execute it. So you can't decide whose style it would be. if it is a style it would be [1, 'a']'s, and if it is b style it would be an error. and therefore it cannot be decided who will get the priority. So I don't personally agree with that statement. because when you take the call stack a is in a higher place than b. when there is a expression like a + b you first call a.__add__(self, other) if a.__add__ is NotImplemented (in this case it is implemented). then you call a.__radd__(self, other). which means call other.__add__ in this case b.__add__. I am telling this based on the place of the call stack and the python community may have more important reasons to do this.
I have face this weird behavior I can not find explications about.
MWE:
l = [1]
l += {'a': 2}
l
[1, 'a']
l + {'B': 3}
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: can only concatenate list (not "dict") to list
Basically, when I += python does not raise an error and append the key to the list while when I only compute the + I get the expected TypeError.
Note: this is Python 3.6.10
l += ... is actually calling object.__iadd__(self, other) and modifies the object in-place when l is mutable
The reason (as #DeepSpace explains in his comment) is that when you do l += {'a': 2} the operation updates l in place only and only if l is mutable. On the other hand, the operation l + {'a': 2} is not done in place resulting into list + dictionary -> TypeError.
(see here)
l = [1]
l = l.__iadd__({'a': 2})
l
#[1, 'a']
is not the same as + that calls object.__add__(self, other)
l + {'B': 3}
TypeError: can only concatenate list (not "dict") to list
So as the authors say this is not a bug. When you Do a += b it is like b come to a's house and changing it the way that a like it to be. what the Authors say is when you do a + b it cannot be decided which one's style will get prioritized. and no one knows where will the result of a + b will go until you execute it. So you can't decide whose style it would be. if it is a style it would be [1, 'a']'s, and if it is b style it would be an error. and therefore it cannot be decided who will get the priority. So I don't personally agree with that statement. because when you take the call stack a is in a higher place than b. when there is a expression like a + b you first call a.__add__(self, other) if a.__add__ is NotImplemented (in this case it is implemented). then you call a.__radd__(self, other). which means call other.__add__ in this case b.__add__. I am telling this based on the place of the call stack and the python community may have more important reasons to do this.
This question already has answers here:
Why can't I use a list as a dict key in python?
(11 answers)
Closed 4 months ago.
Basically I "understand" the ambiguity with using other containers as keys. - do you compare the reference or do you compare the values in it (and how deep).
Well can't you just specialize a list and make a custom comparison/hash operator? As I know in my application I wish to compare the list-of-strings by the value of the strings (and relative ordering of course).
So how would I go about writing a custom hash for these kind of lists? Or in another way - how would I stringify the list without leading to the ambiguity that you get from introducing delimiters (that delimiter might be part of the string)?!
Regarding this question: https://wiki.python.org/moin/DictionaryKeys
There it is said directly that lists can't be used;
That said, the simple answer to why lists cannot be used as dictionary keys is that lists do not provide a valid hash method. Of course, the obvious question is, "Why not?"
So I am writing this to question if there's a way to make lists hashable; and how I would make a satisfactory Hash method.
As an example of why I would want this, see the code below:
namelist = sorted(["Jan", "Frank", "Kim"])
commonTrait = newTraits()
traitdict = {}
traitdict[namelist] = commonTrait;
//later I wish to retrieve it:
trait = traitdict[sorted(["Jan", "Frank", "Kim"])]
In this direct use I have in mind the "ordering of the list" doesn't really matter (sorting is just done in above code to make the lists always equal if they hold the same content).
If you need to use a collection of strings as a dictionary key, you have 2 obvious choices: if the order matters, use tuple:
>>> d[tuple(['foo', 'bar'])] = 'baz'
>>> d['foo', 'bar']
baz
>>> d['bar', 'foo']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: ('bar', 'foo')
If the order must not matter, use frozenset:
>>> d[frozenset(['foo', 'bar'])] = 'baz'
>>> d[frozenset(['bar', 'foo'])]
'baz'
>>> d[frozenset(['bar', 'foo', 'bar'])]
'baz'
If the count matters but ordering does not, use sorted with a tuple:
>>> d[tuple(sorted(['foo', 'bar']))] = 'baz'
>>> d[tuple(sorted(['bar', 'foo']))]
'baz'
>>> d[tuple(sorted(['bar', 'foo', 'bar']))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: ('bar', 'bar', 'foo')
Unlike with Perl hashes, or JavaScript object properties, you do not need to stringify dictionary keys in Python.
Now, concerning the mutable list not being hashable: The Python dictionary implementation uses the hashtable structure. It specifically requires and assumes of the key that:
the key implements the __hash__ method that returns an integer
that the integers should be as widely spread in the output range, and uniformly mapped, as possible.
that __hash__ method returns an unchanging number for the same object
a == b implies that a.__hash__() == b.__hash__()
A list is not usable as a dictionary key, as:
>>> [].__hash__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable
The list class cannot provide a __hash__ method that could satisfy all the requirements simultaneously a == b needs to imply that a.__hash__() == b.__hash__().
(It *could provide an implementation that returns 0 for each list, and it would work correctly then, but it would refute the use of hashing altogether, as all lists would be mapped to the same slot in the dictionary, as the hash codes would break the rule 2).
It is also not possible to create a __hash__ method for a list:
>>> [].__hash__ = lambda x: 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object attribute '__hash__' is read-only
Of course we can always see what would happen if list would have the __hash__ method - we create a subclass of list and provide __hash__ there; the obvious implementation for the hash code would be that of the tuple():
>>> class hashablelist(list):
... def __hash__(self):
... return hash(tuple(self))
...
>>> x = hashablelist(['a', 'b', 'c'])
>>> y = hashablelist(['a', 'b', 'd'])
>>> d = {}
>>> d[x] = 'foo'
>>> d[y] = 'bar'
>>> d.items()
[(['a', 'b', 'c'], 'foo'), (['a', 'b', 'd'], 'bar')]
>>> y[2] = 'c'
>>> d.items()
[(['a', 'b', 'c'], 'foo'), (['a', 'b', 'c'], 'bar')]
>>> del d[x]
>>> del d[y]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: ['a', 'b', 'c']
>>> d.items()
[(['a', 'b', 'c'], 'bar')]
>>> x in d
False
>>> y in d
False
>>> x in d.keys()
True
>>> y in d.keys()
True
The code shows that we just managed to get a broken dictionary as a result - there is no way of accessing or removing the ['a', 'b', 'c'] -> 'bar' pair directly by the key, even though it is visible in .keys(), .values() and .items().
I want to append multiple elements to my list at once. I tried this
>>> l = []
>>> l.append('a')
>>> l
['a']
>>> l.append('b').append('c')
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
l.append('b').append('c')
AttributeError: 'NoneType' object has no attribute 'append'
>>>
how can I append 'b' and 'c' at once?
The method append() works in place. In other words, it modifies the list, and doesn't return a new one.
So, if l.append('b') doesn't return anything (in fact it returns None), you can't do:
l.append('b').append('c')
because it will be equivalent to
None.append('c')
Answering the question: how can I append 'b' and 'c' at once?
You can use extend() in the following way:
l.extend(('b', 'c'))
Use list.extend:
>>> l = []
>>> l.extend(('a', 'b'))
>>> l
['a', 'b']
Note that similar to list.append, list.extend also modifies the list in-place and returns None, so it is not possible to chain these method calls.
But when it comes to string, their methods return a new string. So, we can chain methods call on them:
>>> s = 'foobar'
>>> s.replace('o', '^').replace('a', '*').upper()
'F^^B*R'
l = []
l.extend([1,2,3,4,5])
There is a method for your purpose.