I'm trying to make a new immutable type, similar to the built-in Fraction
but not derived from it. The Fraction class is created like this:
# We're immutable, so use __new__ not __init__
def __new__(cls, numerator=0, denominator=None):
...
self = super(Fraction, cls).__new__(cls)
self._numerator = ...
self._denominator = ...
return self
but I don't see how this is any different from
def __init__(self, numerator=0, denominator=None):
...
self._numerator = ...
self._denominator = ...
Creating 2 Fraction
objects with the same value does not create 2 labels pointing to the same object/memory location (Actually it was pointed out in the comments that it is not common for types to do this.)
Despite the source code comment, they aren't actually immutable:
f1 = Fraction(5)
f2 = Fraction(5)
id(f1), id(f2)
Out[35]: (276745136, 276745616)
f1._numerator = 6
f1
Out[41]: Fraction(6, 1)
f2
Out[42]: Fraction(5, 1)
id(f1)
Out[59]: 276745136
So what's the point of doing it this way?
The docs say
__new__()
is intended mainly to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation. It is also
commonly overridden in custom metaclasses in order to customize class
creation.
So if I'm not subclassing a built in type, but I'm making an immutable type from scratch (subclass of object
), do I still need to use it?
If you are making a truly immutable type, you should use __new__
, because the self object passed into __init__
would logically be immutable already, so it would be too late to assign the values to its members. This is more drastic for those writing subclasses, because adding members would be forbidden.
Since immutability is actually not an intrinsic property, but a trick, generally enforced by hooking __setattr__
, people do write immutable types that initialize with __init__
and then make themselves immutable by setting some member that then makes setting other members impossible. But the logic in such situations can become quite tortuous, and __setattr__
can become riddled with extra rules.
It makes more sense to have some kind of mutable type, and inherit the immutable type from it with the version of __setattr__
that just raises's an exception included in the subclass. This makes the logic of using __new__
obvious. Since it can make the mutable superclass and modify it, but then return it as the inherited type, it is less confusing.
If Fraction intended to be immutable, the implementers either missed that step, or thought better of it later and forgot to remove their comment.
>>> class Pair(object):
... def __init__(self, key, value):
... self.key = key
... self.value = value
...
>>> class ImPair(Pair):
... def __new__(cls, key, value):
... self = Pair(key, value)
... self.__class__ = cls
... def __setattr__(self, name, value):
... raise AttributeError(name)
...
>>> x = Pair(2,3)
>>> x.key
2
>>> x.key = 9
>>> x.key
9
>>> x = ImPair(2,3)
>>> x.key
2
>>> x.key = 9
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __setattr__
AttributeError: key
>>>