Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Fraction use __new__ instead of __init__?

Tags:

python

class

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?

like image 944
endolith Avatar asked Sep 17 '14 23:09

endolith


1 Answers

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
>>>
like image 162
Jon Jay Obermark Avatar answered Nov 14 '22 22:11

Jon Jay Obermark