2

I found this phrase in module fraction.py of standard library:

class Fraction(numbers.Rational):
    ...

    # We're immutable, so use __new__ not __init__
    def __new__(cls, numerator=0, denominator=None, *, _normalize=True):
        """Constructs a Rational.
        ...

I am confused with it because Fraction instance, strictly speaking, is not immutable:

import fractions

fraction = fractions.Fraction(3, 2)
fraction._numerator = 1  # it works
print(fraction)  # 1/2

So, I'm asking for more clarification why __new__ is used instead of __init__ and what does this phrase mean here.

sanyassh
  • 8,100
  • 13
  • 36
  • 70

1 Answers1

2

It's logically immutable, not enforced. It's basically impossible to really enforce immutability in Python. Crazier enforcement attempts can always be met with crazier bypasses. For example, there's a bit of "enforcement" in that numerator and denominator are properties with no setter, but you've already found the bypass for that.

If you really want to screw with a Fraction object, you can, and you can do a lot worse than setting _numerator, but you're asking for problems.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • 1
    I don't know how this answers my question "why `__new__` is used instead of `__init__`". I know that it is impossible to enforce immutability, that is why I am asking. Why not just use `__init__`? – sanyassh Aug 31 '19 at 20:37
  • @sanyash: Because immutable objects are supposed to use `__new__`. `__init__` is a mutator method. – user2357112 Aug 31 '19 at 20:38
  • It sounds better. So if object is logically immutable, but not really, it is just logically better if it uses `__new__` rather than `__init__` but it is not necessary and it can use `__init__` without problems, right? – sanyassh Aug 31 '19 at 20:41
  • 1
    It may be important to use `__new__` so that the immutable interface is shown to subclasses as well as external users of a class. If you used, `__init__`, all of subclasses would probably need to as well, and that might expose the actual mutability of the class in undesirable ways. For instance, a subclass that adds each of its instances to a set might find the hash of the instance change out from under it if it did so in `__new__` but the hash changes after `__init__` was run. – Blckknght Aug 31 '19 at 20:43
  • 1
    @sanyash: It can use `__init__` without everything immediately exploding, but that's a very low bar to clear. Using `__init__` for something that should be immutable makes a mess of things like inheritance. – user2357112 Aug 31 '19 at 20:46
  • @Blckknght and user2357112, thank you, now it makes sense for me. – sanyassh Aug 31 '19 at 20:48
  • @user2357112 if you care, you can include your comments to your answer to make it acceptable. – sanyassh Aug 31 '19 at 20:51