1

I am reading the book Mastering Object-Oriented Python. In the part of book where it talks about the __new__ method and immutable objects, it says something like the __new__() method is used to extend the immutable classes where the __init__() method can't easily be overridden. It also gives an example as below

class Float_Fail(float):
    def __init__(self, value, unit):
        super().__init__(value)
        self.unit = unit

f = Float_Fail(6.5, "knots")

TypeError Traceback (most recent call last)
<ipython-input-2-9511e6ca03be> in <module>()
----> 1 f = Float_Fail(6.5, "knots")
TypeError: float() takes at most 1 argument (2 given)

I'm just curious why float gives this error. I mean, we can initialize a float like this:

f = float(1.1)

Doesn't it mean the __init__() method of float class takes the number as it's second argument. If not, how is the initialization of an immutable class implemented in Python?

tijko
  • 7,599
  • 11
  • 44
  • 64
David Zheng
  • 797
  • 7
  • 21
  • Have you read [the documentation](https://docs.python.org/2/reference/datamodel.html#basic-customization) on `__new__` and `__init__`? Hint: `__new__` is called *before* `__init__`. – BrenBarn Jun 21 '15 at 18:17
  • I know __new__ is called before __init__, my question is when we initialize a float like this, f = float(1.1), in which method does the value 1.1 passed to the float object if not in the __init__ method? – David Zheng Jun 21 '15 at 19:21
  • `__new__`. Because `__new__` is called **before** `__init__`. – BrenBarn Jun 21 '15 at 19:33

2 Answers2

3

If you do MyClass(1), then 1 is indeed passed as an argument to __init__. But __new__ is called before __init__, so 1 is first passed as an argument to __new__. The error you are seeing is because you didn't override __new__, so the inherited float.__new__ is being called, and it doesn't accept your extra argument. It never gets a chance to even try calling __init__, because it fails when trying to call __new__.

This is explained in the documentation. As clearly stated there, the arguments you pass when instiating a class (e.g., the 1 in MyClass(1)) are passed to both __new__ and __init__, in that order.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • Ok, that make sense. But I have a new question. If we can initialize a float object using float(1.1), does that mean the original __new__ method takes an extra argument? Obviously it doesn't. I'm curious how in this case, the value 1.1 is used in float initialization? – David Zheng Jun 21 '15 at 20:28
  • @Brian: I don't understand what you mean by "an extra argument". "Extra" beyond what? – BrenBarn Jun 21 '15 at 20:36
  • The \__new__() method of the float class only takes a single parameter called type, why there isn't a parameter which corresponds to 1.1 in the example, float(1.1)? – David Zheng Jun 21 '15 at 21:00
  • in python3 interactive session,>>> import inspect >>> inspect.getargspec(float.__new__) ArgSpec(args=['type'], varargs='args', keywords='kwargs', defaults=None) >>> inspect.getargspec(float.__init__) ArgSpec(args=['self'], varargs='args', keywords='kwargs', defaults=None) – David Zheng Jun 22 '15 at 12:49
  • @Brian: Ah, I see. That signature is misleading, though. Note that it also accepts varargs and kwargs. If you look at `float.__new__.__doc__`, you'll see that it tells you to look at the docs for the type (i.e., `help(float)`) to see the real signature, and `help(float)` shows that it accepts an argument for the value. – BrenBarn Jun 22 '15 at 17:49
0

By the time the __init__ method runs, the object already exists. Since the object is immutable, its value can't be changed in __init__.

kindall
  • 178,883
  • 35
  • 278
  • 309