2

I'm a little confused by the following example from the python documentation here.

>>> class inch(float):
...     "Convert from inch to meter"
...     def __new__(cls, arg=0.0):
...         return float.__new__(cls, arg*0.0254)
...
>>> print inch(12)
0.3048
>>> 

Presumably, float is here is the actual float class defined somewhere deep inside Python. When we call float.__new__(cls, argument) , we're sneakily calling the function that returns instances of float for a given argument, but we're passing it the inch class instead of the float class. Since the inch class doesn't really do anything, why does this work?

Greg Pallis
  • 247
  • 2
  • 10

2 Answers2

7

Because inch is a subclass of float, it satisfies all the requirements that the float.__new__() instance factory has. It is the job of the __new__(cls) static method to create instances of the first argument, not of it's 'own' class.

Note the word 'static method' there. The __new__ factory is really just a specialist function tied to a class only for inheritance reasons. In other words, it is a function that plays well in a object-oriented hierarchy. You are supposed to find it via super() or perhaps call it directly (as done here). The following would actually be a little more pythonic:

def __new__(cls, arg=0.0):
    return super(inch, cls).__new__(cls, arg*0.0254)

because that would call the 'correct' __new__ function if inch were to be used in a multiple-inheritance hierarchy; in this simple example it'll end up calling float.__new__ just the same.

So, __new__(cls, ...) is expected to create an instance of type cls. Why then tie it to a class at all and not make it a more generic function then? Because in the case of float.__new__(cls, value) it not only creates a new instance of type cls, it also sets it's initial value to value. And in order for that to work, float.__new__(...) needs to have intimate knowledge of what the float class looks like. Because inch() is a subclass of float(), it has the exact same necessary bits to be a float() too, and thus when the float.__new__() factory creates a new inch instance, all those bits are there to make it a inch() instance instead.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Fantastic! This makes so much more sense now. So, to make sure I understand now - __new__ is a static method, which is to say it has no interest in (and no argument for) 'self'. When I overwrite it for this particular class, it presumably stays static (since one could hardly have a __new__ method that required an instance). How does python know to do this without any decorators? – Greg Pallis Nov 23 '12 at 20:49
  • Because `__new__` is special. It is automatically a static method, see the documentation I linked in my answer. – Martijn Pieters Nov 23 '12 at 21:00
  • `__new__` is double special. It could have been a classmethod. The ability to call parent `__new__` without `super()` while passing `cls` in it (e.g., `float.__new__(cls, ..)`) might be the reason [why `__new__` is not a classmethod](http://stackoverflow.com/questions/9092072/why-isnt-new-in-python-new-style-classes-a-class-method). – jfs Nov 23 '12 at 21:40
  • Aha - right there in the first line: "a static method (special-cased so you need not declare it as such)". Thanks guys! – Greg Pallis Nov 23 '12 at 22:19
0

A little background is needed to answer this question:

  1. Only object.__new__() can create a new instances type of objects, this kind of objects cannot be subclassed.
  2. An instance has a type, which can be assigned when passing the type name cls to __new__(cls) as the first argument. class keyword creats another kind of objects: classes (a.k.a types), and these kinds of objects can be subclassed.

Now, go back to your example, what

float.__new__(cls, argument)

essentially does is using object.__new__(cls) to create a new instance (float.__base__ is object), assign the type cls (inch in this case) to it, and also does something with argument defined in float.__new__.

So it is not surprising that it'd work when cls isn't float but inch: the class/type is already created by class inch(float), you are just assigning this type to a new instance.

K Z
  • 29,661
  • 8
  • 73
  • 78