Mastering Objectoriented Python
上QQ阅读APP看书,第一时间看更新

The __new__() method and immutable objects

One use case for the __new__() method is to initialize objects that are otherwise immutable. The __new__() method is where our code can build an uninitialized object. This allows processing before the __init__() method is called to set the attribute values of the object.

The __new__() method is used to extend the immutable classes where the __init__() method can't easily be overridden.

The following is a class that does not work. We'll define a version of float that carries around information on units:

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

We're trying (improperly) to initialize an immutable object.

The following is what happens when we try to use this class definition:

>>> s2 = Float_Fail( 6.5, "knots" )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: float() takes at most 1 argument (2 given)

From this, we see that we can't easily override the __init__() method for the built-in immutable float class. We'd have similar problems with all other immutable classes. We can't set the attribute values on the immutable object, self, because that's the definition of immutability. We can only set attribute values during the object construction. Enter the __new__() method after this.

The __new__() method is auto-magically a static method. This is true without using the @staticmethod decorator. It doesn't use a self variable, as its job is to create the object that will eventually be assigned to the self variable.

For this use case, the method signature is __new__( cls, *args, **kw ). The cls parameter is the class for which an instance must be created. For the metaclass use case in the next section, the args sequence of values are more complex than shown here.

The default implementation of __new__() simply does this: return super().__new__( cls ). It delegates the operation to the superclass. The work winds up getting delegated to object.__new__(), which builds a simple, empty object of the required class. The arguments and keywords to __new__(), with the exception of the cls argument, will be passed to __init__() as part of the standard Python behavior.

With two notable exceptions, this is exactly what we want. The following are the exceptions:

  • When we want to subclass an immutable class definition. We'll dig into that later.
  • When we need to create a metaclass. That's the subject of the next section, as it's fundamentally different from creating immutable objects.

Instead of overriding __init__() when creating a subclass of a built-in immutable type, we have to tweak the object at the time of the creation by overriding __new__(). The following is an example class definition that shows us the proper way to extend float:

class Float_Units( float ):
    def __new__( cls, value, unit ):
       obj= super().__new__( cls, value )
       obj.unit= unit
       return obj

In the preceding code, we set the value of an attribute during the creation of an object.

The following code snippet gives us a floating-point value with attached units information:

>>> speed= Float_Units( 6.5, "knots" )
>>> speed
6.5
>>> speed * 10
65.0
>>> speed.unit
'knots'

Note that an expression such as speed * 10 does not create a Float_Units object. This class definition inherits all the operator special methods from float; the float arithmetic special methods all create float objects. Creating Float_Units objects is the subject of Chapter 7, Creating Numbers.