Mailing List Archive

Are instances of user-defined classes mutable?
the doc of dictionary said "if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key."
i think a instance of user-defined class is mutable, but i found it can be placed into a tuple that as a key of a dict:
>>> class mycls(object):
... a = 1
...
>>> me = mycls()
>>> me.a = 2 # mutable?
>>> {(1, me): 'mycls'}
{(1, <__main__.mycls object at 0x0000022824DAD668>): 'mycls'}
>>>


So are instances of user-defined classes mutable or immutable?

--
https://mail.python.org/mailman/listinfo/python-list
Re: Are instances of user-defined classes mutable? [ In reply to ]
On 06/08/2020 05:17, ZHAOWANCHENG wrote:
> the doc of dictionary said "if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key."
> i think a instance of user-defined class is mutable, but i found it can be placed into a tuple that as a key of a dict:
> >>> class mycls(object):
> ... a = 1
> ...
> >>> me = mycls()
> >>> me.a = 2 # mutable?
> >>> {(1, me): 'mycls'}
> {(1, <__main__.mycls object at 0x0000022824DAD668>): 'mycls'}
> >>>
>
>
> So are instances of user-defined classes mutable or immutable?
>
user class instances are clearly mutable, and in my python 3.8 you can do horrid things like this

>>>> class H:
> ... a = 1
> ... def __hash__(self):
> ... return hash(self.a)
> ...
>>>> h = H()
>>>> hash(h)
> 1
>>>> h.a =2
>>>> hash(h)
> 2
>>>> t=(1,h)
>>>> d={t:23}
>>>> d
> {(1, <__main__.H object at 0x7f5bf72021f0>): 23}
>>>> hash(h)
> 2
>>>> hash(list(d.keys())[0])
> -3550055125485641917
>>>> h.a=33
>>>> hash(list(d.keys())[0])
> -3656087029879219665
>>>>
so the dict itself doesn't enforce immutability of its keys
--
Robin Becker

--
https://mail.python.org/mailman/listinfo/python-list
Re: Are instances of user-defined classes mutable? [ In reply to ]
On 8/6/20 12:17 AM, ZHAOWANCHENG wrote:
> the doc of dictionary said "if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key."
> i think a instance of user-defined class is mutable, but i found it can be placed into a tuple that as a key of a dict:
> >>> class mycls(object):
> ... a = 1
> ...
> >>> me = mycls()
> >>> me.a = 2 # mutable?
> >>> {(1, me): 'mycls'}
> {(1, <__main__.mycls object at 0x0000022824DAD668>): 'mycls'}
> >>>
>
>
> So are instances of user-defined classes mutable or immutable?
>
That documentation isn't strictly correct. All the elements of the tuple
need to be hashable. Generally imutable objects are hashable and mutable
objects are non-hashable, but user defined classes can be hashable, and
in fact are hashable if they don't define an __eq__ member. Classes
without an __eq__ members, use comparison to id() for equality, so
changing a property hasn't 'mutated' the object as far as the dictionary
is concerned.

Hashability is a key requirement for a dictionary, and for an object to
be properly hashable, its hash must not change over the life of the
object, and two objects that are equal, must have the same hash.

Dictionaries actually need a bit more that just hashable to work right,
dictionaries use the hash of the object to first determine where to
store the item, but then uses the equality relationship. For the
dictionary to work right, the set of other objects that it compares
equal to shouldn't change over the life of the object.

Since by default, user classes use their id() for equality, that meets
that requirement. To find that element in the dictionary, you would need
to build a tuple using that exact same me object, you couldn't create
another object, and set it to the same 'value', as they won't compare equal.

--
Richard Damon

--
https://mail.python.org/mailman/listinfo/python-list
Re: Are instances of user-defined classes mutable? [ In reply to ]
On 8/6/20 7:40 AM, Robin Becker wrote:
> On 06/08/2020 05:17, ZHAOWANCHENG wrote:

>> So are instances of user-defined classes mutable or immutable?
>
> user class instances are clearly mutable, and in my python 3.8 you can
> do horrid things like this
>
> [snip buggy/incorrect uses of __hash__]

You can do horrid things like that clear back in Python 2, but the fault
lies with the programmer for misusing the __dunders__.

--
~Ethan~
--
https://mail.python.org/mailman/listinfo/python-list
Re: Are instances of user-defined classes mutable? [ In reply to ]
On Thu, Aug 06, 2020 at 12:17:17PM +0800, ZHAOWANCHENG wrote:
> the doc of dictionary said "if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key."
> i think a instance of user-defined class is mutable, but i found it can be placed into a tuple that as a key of a dict:
> >>> class mycls(object):
> ... a = 1
> ...
> >>> me = mycls()
> >>> me.a = 2 # mutable?
> >>> {(1, me): 'mycls'}
> {(1, <__main__.mycls object at 0x0000022824DAD668>): 'mycls'}
> >>>
>
>
> So are instances of user-defined classes mutable or immutable?

A more specific description of objects that can be keys in a dictionary
can be found on the documentation:

https://docs.python.org/3/library/stdtypes.html#mapping-types-dict

To be a key in a dictionary, an object must be *hashable*. The spec for
hashable objects is also in the documentation:

https://docs.python.org/3/glossary.html#term-hashable

In short, if a class implements __hash__() (which should never change
during an object's lifetime) and __eq__(), it can be hashable.

Many python builtins are hashable, but mutable builtins aren't. For
example lists aren't hashable.


To investigate a bit further, we might examine a particular example more
closely. In the code snippet that follows, we make two tuples with the
same data and two instances of a user-defined class with the same data.
We make a dictionary and see the difference between using tuples as keys
and instances of a user-defined class as keys.

```
tup1 = (1, 2)
tup2 = (1, 2)
d = dict()
d[tup1] = "one"
print(d[tup2]) # prints "one"
# Note that d "knows" tup1 and tup2 are "the same"

class myclass:
def __init__(self):
self.data = [1, 2]

myobj1 = myclass()
myobj2 = myclass()
print(myobj1.data == myobj2.data) # True
d[myobj1] = "spam"
print(d[myobj2]) # raises KeyError as myobj2 not in dictionary
# d doesn't "know" that myobj1 and myobj2 are "the same"
```

This difference in behavior is due to the default __hash__ and __eq__
methods for myclass, which don't consider the attributes of each
instance, whereas the hashing and comparison of tuples does consider the
contents of the tuple.

- DLD

--
David Lowry-Duda <david@lowryduda.com> <davidlowryduda.com>
--
https://mail.python.org/mailman/listinfo/python-list
Re: Are instances of user-defined classes mutable? [ In reply to ]
On 8/6/20 10:40 AM, Robin Becker wrote:
> On 06/08/2020 05:17, ZHAOWANCHENG wrote:
>> the doc of dictionary said "if a tuple contains any mutable object
>> either directly or indirectly, it cannot be used as a key."
>> i think a instance of user-defined class is mutable, but i found it
>> can be placed into a tuple that as a key of a dict:
>>      >>> class mycls(object):
>>      ...     a = 1
>>      ...
>>      >>> me = mycls()
>>      >>> me.a = 2  # mutable?
>>      >>> {(1, me): 'mycls'}
>>      {(1, <__main__.mycls object at 0x0000022824DAD668>): 'mycls'}
>>      >>>
>>
>>
>> So are instances of user-defined classes mutable or immutable?
>>
> user class instances are clearly mutable, and in my python 3.8 you can
> do horrid things like this
But since the 'mutation' doesn't affect the hash or the equality tests
on the object, is it really a mutation?
>
>>>>> class H:
>> ...      a = 1
>> ...      def __hash__(self):
>> ...          return hash(self.a)
>> ...
>>>>> h = H()
>>>>> hash(h)
>> 1
>>>>> h.a =2
>>>>> hash(h)
>> 2
>>>>> t=(1,h)
>>>>> d={t:23}
>>>>> d
>> {(1, <__main__.H object at 0x7f5bf72021f0>): 23}
>>>>> hash(h)
>> 2
>>>>> hash(list(d.keys())[0])
>> -3550055125485641917
>>>>> h.a=33
>>>>> hash(list(d.keys())[0])
>> -3656087029879219665
>>>>>
> so the dict itself doesn't enforce immutability of its keys
Yes, here you have defined a hash that violates the requirements of the
Dictionary (and most things that use hashes) so your class is broken,
and you can expect to get strangeness out of your dictionary.
> --
> Robin Becker
>

--
Richard Damon

--
https://mail.python.org/mailman/listinfo/python-list
Re:Re: Are instances of user-defined classes mutable? [ In reply to ]
At 2020-08-06 23:17:57, "Richard Damon" <Richard@Damon-Family.org> wrote:

>On 8/6/20 10:40 AM, Robin Becker wrote:
>> On 06/08/2020 05:17, ZHAOWANCHENG wrote:
>>> the doc of dictionary said "if a tuple contains any mutable object
>>> either directly or indirectly, it cannot be used as a key."
>>> i think a instance of user-defined class is mutable, but i found it
>>> can be placed into a tuple that as a key of a dict:
>>> >>> class mycls(object):
>>> ... a = 1
>>> ...
>>> >>> me = mycls()
>>> >>> me.a = 2 # mutable?
>>> >>> {(1, me): 'mycls'}
>>> {(1, <__main__.mycls object at 0x0000022824DAD668>): 'mycls'}
>>> >>>
>>>
>>>
>>> So are instances of user-defined classes mutable or immutable?
>>>
>> user class instances are clearly mutable, and in my python 3.8 you can
>> do horrid things like this
>But since the 'mutation' doesn't affect the hash or the equality tests
>on the object, is it really a mutation?
>>
>>>>>> class H:
>>> ... a = 1
>>> ... def __hash__(self):
>>> ... return hash(self.a)
>>> ...
>>>>>> h = H()
>>>>>> hash(h)
>>> 1
>>>>>> h.a =2
>>>>>> hash(h)
>>> 2
>>>>>> t=(1,h)
>>>>>> d={t:23}
>>>>>> d
>>> {(1, <__main__.H object at 0x7f5bf72021f0>): 23}
>>>>>> hash(h)
>>> 2
>>>>>> hash(list(d.keys())[0])
>>> -3550055125485641917
>>>>>> h.a=33
>>>>>> hash(list(d.keys())[0])
>>> -3656087029879219665
>>>>>>
>> so the dict itself doesn't enforce immutability of its keys
>Yes, here you have defined a hash that violates the requirements of the
>Dictionary (and most things that use hashes) so your class is broken,
>and you can expect to get strangeness out of your dictionary.
>> --
>> Robin Becker
>>
>
>--
>Richard Damon
>
>--

>https://mail.python.org/mailman/listinfo/python-list


So instances of user-defined classes are immutable by default?


Or the description of "if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key." in the document is not completely correct?


The description mentioned above comes from here: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
--
https://mail.python.org/mailman/listinfo/python-list
Re: Are instances of user-defined classes mutable? [ In reply to ]
On 8/6/20 8:53 PM, ZHAOWANCHENG wrote:

> So instances of user-defined classes are immutable by default?

No, they're clealry mutable.

> Or the description of "if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key." in the document is not completely correct?

It's correct, but is not relevant to your case, as you're asking about
an object different than a tuple.

When defining a class, you get by default a hash that is consistent, no
matter what you do to the contents of an instance, so instances are
eligible to be used as dict keys. You can define the class in a way
that instances cannot be used as keys, by ensuring there is no hash
function.

--
https://mail.python.org/mailman/listinfo/python-list
Re: Are instances of user-defined classes mutable? [ In reply to ]
On 8/6/20 10:53 PM, ZHAOWANCHENG wrote:
> So instances of user-defined classes are immutable by default?
> Or the description of "if a tuple contains any mutable object either
> directly or indirectly, it cannot be used as a key." in the document
> is not completely correct?
> The description mentioned above comes from here:
> https://docs.python.org/3/tutorial/datastructures.html#dictionaries

My answer was pointing out in a very abstract sense, it is perhaps
tricky to define what is 'mutability' for a class as it affects its use
in a dictionary.

The key here is to ask what does using a class as a key (or part of a
key with a tuple) means.

You build the dictionary, use a key value, and then you can look up that
entry by passing an equal value key, and it will find the term.

So we need to look at what it means for class objects to be equal, and
the default for that checks for equality of the id(), not any of the
members.

This means that doing something like:

a = myclass(10)

b = myclass(10)

d = { a: 1}

trying to do a

d[b] will throw a KeyError exception, as there is no item in the dict
with that key.

and if we ask a == b we get false (even though all their values match)

This comes to the point that, for a class with the default equality
operator, so equality is identity, do changing a member of a class
object REALLY mutate the object as far as the dictionary is concerned?
Changing the member value does NOT change what the key represents.

Note also, once you define the equality testing function __eq__, then
python makes the class non-hashable (unless you also define a __hash__
function) so a class like that can't be used as a part of a key in a
dictionary, because it now has the ability to 'change' the value it
represents.

Going back to the original issue. A tuple can normally be used as a
dictionary key (but a list can not) because it can build its hash as a
combination of the hash for all its elements (as long as they are
hashable, and the 'immutable' objects are) and thus be usable as a key.
Since a list, unlike a tuple, allows you to mutate  the sequence by
modifying the list, it can't provide the unchangable hash by the same
method.

When you put a class object into the tuple, as long as it still has it
__hash__ member, because you haven't defined a __eq__ member, is at
least to the tuple, immutable, as the only part of it that matters is
the value of id() which WILL be unchanging.

--
Richard Damon

--
https://mail.python.org/mailman/listinfo/python-list