dictionary keys: custom objects vs lists - python

I have read that lists cannot be dictionary keys because mutable objects cannot be hashed.
However, custom objects appear to be mutable as well:
# custom object
class Vertex(object):
def __init__(self, key):
self.key = key
v = Vertex(1)
v.color = 'grey' # this line suggests the custom object is mutable
But, unlike lists, they can be used as dictionary keys; why is this? Couldn't we simply hash some sort of id (such as the address of the object in memory) in both cases?

as noted in Why Lists can't be Dictionary Keys:
Lists as Dictionary Keys
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?"
Consider what kinds of hash functions could be provided for lists.
If lists hashed by id, this would certainly be valid given Python's definition of a hash function -- lists with different hash values would have different ids. But lists are containers, and most other operations on them deal with them as such. So hashing lists by their id instead would produce unexpected behavior such as:
Looking up different lists with the same contents would produce different results, even though comparing lists with the same contents would indicate them as equivalent.
Using a list literal in a dictionary lookup would be pointless -- it would always produce a KeyError.
User Defined Types as Dictionary Keys
What about instances of user defined types?
By default, all user defined types are usable as dictionary keys with hash(object) defaulting to id(object), and cmp(object1, object2) defaulting to cmp(id(object1), id(object2)). This same suggestion was discussed above for lists and found unsatisfactory. Why are user defined types different?
In the cases where an object must be placed in a mapping, object identity is often much more important than object contents.
In the cases where object content really is important, the default settings can be redefined by overridding __hash__ and __cmp__ or __eq__.
Note that it is often better practice, when an object is to be associated with a value, to simply assign that value as one of the object's attributes.

Related

can i use dictionary as a value of set?

as you can see that i am trying to use dictionary in set as a value but it is showing error i want to know that why it is not possible to use dictionary as a value of set ? i want to know why ? is there any reason ? it is not working at all and so many errors are comming So please help me what is the problem ? why can not i use dictionary as a value of set sequence ? but it is working with list and tuple but it is not working only with set why?
s={1,2,4,{1:'fc',2:'tw'},'co-operator'}
print(s)
A set requires that all elements in it are hashable. A dictionary is not hashable.
An object is hashable if it has a hash value which never
changes during its lifetime (it needs a hash() method), and can be
compared to other objects (it needs an eq() method). Hashable
objects which compare equal must have the same hash value.
Hashability makes an object usable as a dictionary key and a set
member, because these data structures use the hash value internally.
Most of Python’s immutable built-in objects are hashable; mutable
containers (such as lists or dictionaries) are not; immutable
containers (such as tuples and frozensets) are only hashable if their
elements are hashable. Objects which are instances of user-defined
classes are hashable by default. They all compare unequal (except with
themselves), and their hash value is derived from their id().

In Python, why is a tuple hashable but not a list?

Here below when I try to hash a list, it gives me an error but works with a tuple. Guess it has something to do with immutability. Can someone explain this in detail ?
List
x = [1,2,3]
y = {x: 9}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Tuple
z = (5,6)
y = {z: 89}
print(y)
{(5, 6): 89}
Dicts and other objects use hashes to store and retrieve items really quickly. The mechanics of this all happens "under the covers" - you as the programmer don't need to do anything and Python handles it all internally. The basic idea is that when you create a dictionary with {key: value}, Python needs to be able to hash whatever you used for key so it can store and look up the value quickly.
Immutable objects, or objects that can't be altered, are hashable. They have a single unique value that never changes, so python can "hash" that value and use it to look up dictionary values efficiently. Objects that fall into this category include strings, tuples, integers and so on. You may think, "But I can change a string! I just go mystr = mystr + 'foo'," but in fact what this does is create a new string instance and assigns it to mystr. It doesn't modify the existing instance. Immutable objects never change, so you can always be sure that when you generate a hash for an immutable object, looking up the object by its hash will always return the same object you started with, and not a modified version.
You can try this for yourself: hash("mystring"), hash(('foo', 'bar')), hash(1)
Mutable objects, or objects that can be modified, aren't hashable. A list can be modified in-place: mylist.append('bar') or mylist.pop(0). You can't safely hash a mutable object because you can't guarantee that the object hasn't changed since you last saw it. You'll find that list, set, and other mutable types don't have a __hash__() method. Because of this, you can't use mutable objects as dictionary keys:
>>> hash([1,2,3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Eric Duminil's answer provides a great example of the unexpected behaviour that arises from using mutable objects as dictionary keys
Here are examples why it might not be a good idea to allow mutable types as keys. This behaviour might be useful in some cases (e.g. using the state of the object as a key rather than the object itself) but it also might lead to suprising results or bugs.
Python
It's possible to use a numeric list as a key by defining __hash__ on a subclass of list :
class MyList(list):
def __hash__(self):
return sum(self)
my_list = MyList([1, 2, 3])
my_dict = {my_list: 'a'}
print(my_dict.get(my_list))
# a
my_list[2] = 4 # __hash__() becomes 7
print(next(iter(my_dict)))
# [1, 2, 4]
print(my_dict.get(my_list))
# None
print(my_dict.get(MyList([1,2,3])))
# None
my_list[0] = 0 # __hash_() is 6 again, but for different elements
print(next(iter(my_dict)))
# [0, 2, 4]
print(my_dict.get(my_list))
# 'a'
Ruby
In Ruby, it's allowed to use a list as a key. A Ruby list is called an Array and a dict is a Hash, but the syntax is very similar to Python's :
my_list = [1]
my_hash = { my_list => 'a'}
puts my_hash[my_list]
#=> 'a'
But if this list is modified, the dict doesn't find the corresponding value any more, even if the key is still in the dict :
my_list << 2
puts my_list
#=> [1,2]
puts my_hash.keys.first
#=> [1,2]
puts my_hash[my_list]
#=> nil
It's possible to force the dict to calculate the key hashes again :
my_hash.rehash
puts my_hash[my_list]
#=> 'a'
A hashset calculates the hash of an object and based on that hash, stores the object in the structure for fast lookup. As a result, by contract once an object is added to the dictionary, the hash is not allowed to change. Most good hash functions will depend on the number of elements and the elements itself.
A tuple is immutable, so after construction, the values cannot change and therefore the hash cannot change either (or at least a good implementation should not let the hash change).
A list on the other hand is mutable: one can later add/remove/alter elements. As a result the hash can change violating the contract.
So all objects that cannot guarantee a hash function that remains stable after the object is added, violate the contract and thus are no good candidates. Because for a lookup, the dictionary will first calculate the hash of the key, and determine the correct bucket. If the key is meanwhile changed, this could result in false negatives: the object is in the dictionary, but it can no longer be retrieved because the hash is different so a different bucket will be searched than the one where the object was originally added to.
I would like to add the following aspect as it's not covered by other answers already.
There's nothing wrong about making mutable objects hashable, it's just not unambiguous and this is why it needs to be defined and implemented consistently by the programmer himself (not by the programming language).
Note that you can implement the __hash__ method for any custom class which allows its instances to be stored in contexts where hashable types are required (such as dict keys or sets).
Hash values are usually used to decide if two objects represent the same thing. So consider the following example. You have a list with two items: l = [1, 2]. Now you add an item to the list: l.append(3). And now you must answer the following question: Is it still the same thing? Both - yes and no - are valid answers. "Yes", it is still the same list and "no", it has not the same content anymore.
So the answer to this question depends on you as the programmer and so it is up to you to manually implement hash methods for your mutable types.
Based on Python Glossary
An object is hashable if it has a hash value which never changes during its lifetime (it needs a __hash__() method), and can be compared to other objects (it needs an __eq__() method). Hashable objects which compare equal must have the same hash value.
All of Python’s immutable built-in objects are hashable; mutable containers (such as lists or dictionaries) are not.
Because a list is mutable, while a tuple is not. When you store the hash of a value in, for example, a dict, if the object changes, the stored hash value won't find out, so it will remain the same. The next time you look up the object, the dictionary will try to look it up by the old hash value, which is not relevant anymore.
To prevent that, python does not allow you to has mutable items.

How to know when creating set or frozenset

I am new to Python. I am reading Building Skills in Python (Lott) and trying out some examples. I see that the set(iterable) function creates both a mutable set and an immutable frozenset. How do I know if I am creating a set or a frozenset?
That is simply incorrect. The set() built-in returns a set, not a frozenset. frozenset() returns a frozenset. A set and a frozenset are both set types, however they are distinct set types.
The Python docs can always be useful for clarification on things like this, there's an entire list of built-in functions.
Excerpt from the book Building Skills in Python (Lott) noted by OP in a comment, emphasis mine.
A set value is created by using the set() or frozenset() factory
functions. These can be applied to any iterable container, which includes any sequence, the keys of a dict,
or even a file.
The author here is using "set value" to describe a value of set type, and is thus not indicating that set() and frozenset() do the same thing - they produce values of distinct set types, namely sets and frozensets.

when compare by id is used in Python? Dictionary key comparison?

What does this mean?
The only types of values not acceptable as dictionary keys are values containing lists or dictionaries or other mutable types that are compared by value rather than by object identity, the reason being that the efficient implementation of dictionaries requires a key’s hash value to remain constant.
I think even for tuples, comparison will happen by value.
The problem with a mutable object as a key is that when we use a dictionary, we rarely want to check identity. For example, when we use a dictionary like this:
a = "bob"
test = {a: 30}
print(test["bob"])
We expect it to work - the second string "bob" may not be the same as a, but it is the same value, which is what we care about. This works as any two strings that equate will have the same hash, meaning that the dict (implemented as a hashmap) can find those strings very efficiently.
The issue comes into play when we have a list as a key, imagine this case:
a = ["bob"]
test = {a: 30}
print(test[["bob"]])
We can't do this any more - the comparison won't work as the hash of a list is not based on it's value, but rather the instance of the list (aka (id(a) != id(["bob"))).
Python has the choice of making the list's hash change (undermining the efficiency of a hashmap) or simply comparing on identity (which is useless in most cases). Python disallows these specific mutable keys to avoid subtle but common bugs where people expect the values to be equated on value, rather than identity.
The documentation mixes together two different things: mutability, and value-comparable. Let's separate them out.
Immutable objects that compare by identity are fine. The identity can
never change, for any object.
Immutable objects that compare by value are fine. The value can never
change for an immutable object. This includes tuples.
Mutable objects that compare by identity are fine. The identity can
never change, for any object.
Mutable objects that compare by value are not acceptable. The value
can change for a mutable object, which would make the dictionary
invalid.
Meanwhile, your wording isn't quite the same as Mapping Types (4.10 in Python 3.3 or 5.8 in Python 2.7, both of which say:
A dictionary’s keys are almost arbitrary values. Values that are not hashable, that is, values containing lists, dictionaries or other mutable types (that are compared by value rather than by object identity) may not be used as keys.
Anyway, the key point here is that the rule is "not hashable"; "mutable types (that are compared by value rather than by object identity)" is just to explain things a little further. It isn't strictly true that comparing by object identity and hashing by object identity are always the same (the only thing that's required is that if id is equal, the hash is equal).
The part about "efficient implementation of dictionaries" from the version you posted just adds to the confusion (which is probably why it's not in the reference documentation). Even if someone came up with an efficient way to deal with storing lists as dict keys tomorrow, the language doesn't allow it.
A hash is way of calculating an unique code for an object, this code always the same for the same object. hash('test') for example is 2314058222102390712, so is a = 'test'; hash(a) = 2314058222102390712.
Internally a dictionary value is searched by the hash, not by the variable you specify. A list is mutable, a hash for a list, if it where defined, would be changing whenever the list changes. Therefore python's design does not hash lists. Lists therefore can not be used as dictionary keys.
Tuples are immutable, therefore tubles have hashes e.G. hash((1,2)) = 3713081631934410656. one could compare whether a tuple a is equal to the tuple (1,2) by comparing the hash, rather than the value. This would be more efficient as we have to compare only one value instead of two.

How to create an immutable dictionary in python?

I want to subclass dict in python such that all the dictionaries of the sub-class are immutable.
I don't understand how does __hash__ affects the immutability, since in my understanding it just signifies the equality or non-equality of objects !
So, can __hash__ be used to implement immutability ? How ?
Update:
Objective is that common response from an API is available as a dict, which has to be shared as a global variable. So, that needs to be intact no matter what ?
I found a Official reference : suggestion contained in a rejected PEP.
class imdict(dict):
def __hash__(self):
return id(self)
def _immutable(self, *args, **kws):
raise TypeError('object is immutable')
__setitem__ = _immutable
__delitem__ = _immutable
clear = _immutable
update = _immutable
setdefault = _immutable
pop = _immutable
popitem = _immutable
Attribution : http://www.python.org/dev/peps/pep-0351/
It is possible to create immutable dict using just standard library.
from types import MappingProxyType
power_levels = MappingProxyType(
{
"Kevin": 9001,
"Benny": 8000,
}
)
See source of idea with more detailed explanation
In frozendict, hash is simply implemented following the rejected PEP 416 of Victor Stinner:
def __hash__(self):
try:
fs = frozenset(self.items())
except TypeError:
hash = -1
else:
hash = hash(fs)
if hash == -1:
raise TypeError("Not all values are hashable.")
return hash
PS: I'm the new maintainer of the package.
So, can __hash__ be used to implement immutability ?
No, it can't. The object can be made mutable (or not) irrespective of what its __hash__ method does.
The relationship between immutable objects and __hash__ is that, since an immutable object cannot be changed, the value returned by __hash__ remains constant post-construction. For mutable objects, this may or may not be the case (the recommended practice is that such objects simply fail to hash).
For further discussion, see Issue 13707: Clarify hash() constency period.
Regarding the relationship between hashability and mutability:
To be useful, a hash implementation needs to fulfil the following properties:
The hash value of two objects that compare equal using == must be equal.
The hash value may not change over time.
These two properties imply that hashable classes cannot take mutable properties into account when comparing instances, and by contraposition that classes which do take mutable properties into account when comparing instances are not hashable. Immutable classes can be made hashable without any implications for comparison.
All of the built-in mutable types are not hashable, and all of the immutable built-in types are hashable. This is mainly a consequence of the above observations.
User-defined classes by default define comparison based on object identity, and use the id() as hash. They are mutable, but the mutable data is not taken into account when comparing instances, so they can be made hashable.
Making a class hashable does not make it immutable in some magic way. On the contrary, to make a dictionary hashable in a reasonable way while keeping the original comparison operator, you will first need to make it immutable.
Edit: Regarding your update:
There are several ways to provide the equivalent of global immutable dictionary:
Use a collections.namedtuple() instance instead.
Use a user-defined class with read-only properties.
I'd usually go with something like this:
_my_global_dict = {"a": 42, "b": 7}
def request_value(key):
return _my_global_dict[key]
By the leading underscore, you make clear that _my_global_dict is an implementation detail not to be touched by application code. Note that this code would still allow to modify dictionary values if they happen to be mutable objects. You could solve this problem by returning copy.copy()s or copy.deepcopy()s of the values if necessary.
Since Python 3.3, it's possible to use MappingProxyType to create an immutable mapping:
>>> from types import MappingProxyType
>>> MappingProxyType({'a': 1})
mappingproxy({'a': 1})
>>> immutable_mapping = MappingProxyType({'a': 1})
>>> immutable_mapping['a']
1
>>> immutable_mapping['b'] = 2
Traceback (most recent call last):
(...)
TypeError: 'mappingproxy' object does not support item assignment
It's not hashable so you can't use it as a dictionary key (and it's "final", so you can't subclass it to override __hash__), but it's good enough if you want an immutable mapping to prevent accidental modification of a global value (like a class default attribute).
Careful not to add mutable values that could themselves be modified.

Categories