Immutable means that the object, the top-level container for the item, cannot be changed. Note that this applies only to the top level; it may contain references to sub-objects that are mutable.
Hashable has a functional definition: Python's built-in hash
function returns a value. This generally means that the object's closure (following all references to their leaf-node values) consists of immutable objects.
Your premise is incorrect: a tuple can contain mutable items. Any such reference renders the tuple un-hashable.
For instance:
>>> b = [7]
>>> a = (b, 5)
>>> a
([7], 5)
>>> type(a)
<class 'tuple'>
>>> set(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> b[0] = 'b'
>>> a
(['b'], 5)
b
is a mutable list. The reference to b
can be held in the tuple a
. We can change the value of b[0]
(not it's object handle), and the display value of a
changes. However, we cannot make a set including a
, because the mutability of b
renders a
unhashable.
Continuing the example:
>>> b = False
>>> a
(['b'], 5)
>>> b = [14]
>>> a
(['b'], 5)
a
is immutable. Thus, when we change b
, only b
gets the reference to the new object. a
retains the original object handle, still pointing to ['b']
.