As per my understanding Python user defined class instances are by default immutable. Immutable objects does not change their hash value and they can be used as dictionary keys and set elements.
I have below code snippet.
class Person(object):
def __init__(self, name, age):
self.name=name
self.age=age
Now, I will instantiate Person class and create an object and print its hash value.
jane = Person('Jane', 29)
print(jane.__hash__())
-9223371933914849101
Now, I will mutate jane object and print its hash value.
jane.age = 33
print(jane.__hash__())
-9223371933914849101
My question is even if jane object is mutable why its hash value is not changing?
Also, I can use mutable jane object as dict key and set element.
The object remains the same, even if you are changing properties of the object. And no, there are only very few immutable objects in python - frozenset for instance. But classes are not immutable. If you want immutable objects, you have to make them so.
All objects (with the exception of a few in the standard library, some that implement special access mechanisms using things like descriptors and decorators, or some implemented in C) are mutable. This includes instances of user defined classes, classes themselves, and even the type objects that define the classes.
Immutable class in java means that once an object is created, we cannot change its content. In Java, all the wrapper classes (like Integer, Boolean, Byte, Short) and String class is immutable. We can create our own immutable class as well.
All you have to do is sub-class from Freezer . After initialization of the Python, I “freeze” the object with my overriding of the __delattr__ and __setattr__ methods on the object. I set _frozen = True which indicates the instance is now “frozen”. objects become immutable.
To define a class with immutable instances, you can do something like this:
class Person:
"""Immutable person class"""
# Using __slots__ reduces memory usage.
# If __slots__ doesn't include __dict__, new attributes cannot be added.
# This is not always desirable, e.g. it you want to subclass Person.
__slots__ = ('name', 'age')
def __init__(self, name, age):
"""Create a Person instance.
Arguments:
name (str): Name of the person.
age: Age of the person.
"""
# Parameter validation. This shows how to do this,
# but you don't always want to be this inflexibe.
if not isinstance(name, str):
raise ValueError("'name' must be a string")
# Use super to set around __setattr__ definition
super(Person, self).__setattr__('name', name)
super(Person, self).__setattr__('age', int(age))
def __setattr__(self, name, value):
"""Prevent modification of attributes."""
raise AttributeError('Persons cannot be modified')
def __repr__(self):
"""Create a string representation of the Person.
You should always have at least __repr__ or __str__
for interactive use.
"""
template = "<Person(name='{}', age={})>"
return template.format(self.name, self.age)
A test:
In [2]: test = Person('S. Eggs', '42')
In [3]: str(test)
Out[3]: "<Person(name='S. Eggs', age=42)>"
In [4]: test.name
Out[4]: 'S. Eggs'
In [5]: test.age
Out[5]: 42
In [6]: test.name = 'foo'
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-6-1d0482a5f50c> in <module>()
----> 1 test.name = 'foo'
<ipython-input-1-efe979350b7b> in __setattr__(self, name, value)
24 def __setattr__(self, name, value):
25 """Prevent modification of attributes."""
---> 26 raise AttributeError('Persons cannot be modified')
27
28 def __repr__(self):
AttributeError: Persons cannot be modified
The object remains the same, even if you are changing properties of the object. And no, there are only very few immutable objects in python - frozenset for instance. But classes are not immutable.
If you want immutable objects, you have to make them so. E.g. forbid assigning new values to properties are turning new objects in that case.
To achieve this, you can use the underscore convention: Prepend your fields with a "_" - this indicates to other developers that the value is private and should not be changed from the outside.
If you want a class with an unchangeable "name" field you could use this syntax:
class test(object):
def __init__(name):
self._name = name
@property
def name(self):
return self._name
Of course, _name CAN be changed by an dev, but that breaks the visible contract.
The reason is that to make this object hashable, despite the fact that it IS mutable, Python's default __hash__() method calculate the hash value from it's reference ID.
This means that if you change it's content or copy the reference to another name, the hash value won't change, But if you copy it to another place or create another object with the same content, then it's value will be different.
You can change that behaviour by redefining the __hash__() method, but you need to ensure that the object is not mutable or you will break your « named collections » (dictionnaries, sets & their subclasses).
That is not the contract Python goes by From the docs- emphasis added by me on the bolded parts:
object.__hash__(self)
Called by built-in functionhash()
and for operations on members of hashed collections includingset
,frozenset
, anddict. __hash__()
should return an integer. The only required property is that objects which compare equal have the same hash value; it is advised to mix together the hash values of the components of the object that also play a part in comparison of objects by packing them into a tuple and hashing the tuple. Example:def __hash__(self): return hash((self.name, self.nick, self.color)) Note hash() truncates
And some more relevant information:
If a class does not define an
__eq__()
method it should not define a__hash__()
operation either; if it defines__eq__()
but not__hash__()
, its instances will not be usable as items in hashable collections. If a class defines mutable objects and implements an__eq__()
method, it should not implement__hash__()
, since the implementation of hashable collections requires that a key’s hash value is immutable (if the object’s hash value changes, it will be in the wrong hash bucket).
And, to the core of your question:
User-defined classes have
__eq__()
and__hash__()
methods by default; with them, all objects compare unequal (except with themselves) andx.__hash__()
returns an appropriate value such thatx == y
implies both that x is y andhash(x) == hash(y)
.A class that overrides
__eq__()
and does not define__hash__()
will have its__hash__()
implicitly set toNone
. When the__hash__()
method of a class isNone
, instances of the class will raise an appropriateTypeError
when a program attempts to retrieve their hash value, and will also be correctly identified as unhashable when checkingisinstance(obj, collections.Hashable)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With