The __new__() method and metaclasses

The other use case for the __new__() method is to create a metaclass to control how a class definition is built. This use of __new__() to build a class object is related to using __new__() to build a new immutable object, shown previously. In both cases, __new__() gives us a chance to make minor modifications in situations where __init__() isn't relevant.

A metaclass is used to build a class. Once a class object has been built, the class object is used to build instance objects. The metaclass of all class definitions is type. The type() function creates the class objects in an application. Additionally, the type() function can be used to reveal the class of an object.

The following is a silly example of building a new, nearly useless class directly with type() as a constructor:

Useless = type("Useless", (), {}) 

To create a new class, the type() function is given a string name for the class, a tuple of superclasses, and a dictionary used to initialize any class variables. The return value is a class value. Once we've created this class, we can create objects of this Useless class. However, the objects won't do much because they have no methods or attributes.

We can use this newly-minted Useless class to create objects, for what little it's worth. The following is an example:

>>> Useless = type("Useless", (), {}) 
>>> u = Useless()
>>> u.attribute = 1
>>> dir(u)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'attribute']

This example created an instance of Useless, u. It's easy to add an attribute to the objects of this class with an assignment to u.attribute.

This is almost equivalent to defining minimal classes, as follows:

from types import SimpleNamespace
Useless2 = SimpleNamespace

class Useless3: pass

The definition of Useless2 is the SimpleNamespace class from the types module. The definition of Useless3 uses Python syntax to create a class that's the default implementation of object. These all have nearly identical behaviors.

This brings up the important question: why would we change the way classes are defined in the first place?

The answer is that some of the default features of a class aren't perfectly applicable to some edge cases. We'll talk about three situations where we might want to introduce a metaclass:

  • We can use a metaclass to add attributes or methods to a class. Note that we're adding these to the class itself, not to any of the instances of the class. The reason for using a metaclass is to simplify the creation of a large number of similar classes. In many respects, adding a @classmethod decorator to a method can be similar to creating a metaclass.
  • Metaclasses are used to create Abstract Base Classes (ABC), which we'll look at in Chapter 4, Attribute Access, Properties, and Descriptions through Chapter 7, Creating Containers and Collections. An ABC relies on a metaclass __new__() method to confirm that the concrete subclass is complete. We'll introduce this in Chapter 5, The ABCs of Consistent Design.
  • Metaclasses can be used to simplify some aspects of object serialization. We'll look at this in Chapter 10, Serializing and Saving - JSON, YAML, Pickle, CSV, and XML. When a large number of classes will all be using similar serialization techniques, a metaclass can ensure that all of the application classes have a common serialization aspect.

In general, there are a great many things that can be done in a metaclass that cannot be understood by the mypy tool. It's not always helpful to struggle with the details of defining metaclasses.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset