Metaclass example – class-level logger

When we have a large number of classes that all need a logger, it can be handy to centralize the feature in a single definition. There are a number of ways of doing this, one of which is to provide a metaclass definition that builds a class-level logger shared by all instances of the class.

The recipe has the following three parts:

  1. Create a metaclass. The __new__() method of the metaclass will add attributes to the constructed class definition.
  2. Create an abstract superclass that is based on the metaclass. This abstract class will simplify inheritance for the application classes.
  3. Create the subclasses of the abstract superclass that benefit from the metaclass.

The following is an example metaclass, which will inject a logger into a class definition:

import logging

class LoggedMeta(type):

def __new__(
cls: Type,
name: str,
bases: Tuple[Type, ...],
namespace: Dict[str, Any]
) -> 'Logged':
result = cast('Logged', super().__new__(cls, name, bases, namespace))
result.logger = logging.getLogger(name)
return result

class Logged(metaclass=LoggedMeta):
logger: logging.Logger

The LoggedMeta class extends the built-in default metaclass, type, with a new version of the __new__() method.

The __new__() metaclass method is executed after the class body elements have been added to the namespace. The argument values are the metaclass, the new class name to be built, a tuple of superclasses, and a namespace with all of the class items used to initialize the new class. This example is typical: it uses super() to delegate the real work of __new__() to the superclass. The superclass of this metaclass is the built-in type class. 

The __new__() method in this example also adds an attribute, logger, into the class definition. This was not provided when the class was written, but will be available to every class that uses this metaclass.

We must use the metaclass when defining a new abstract superclass, Logged. Note that the superclass includes a reference to the logger attribute, which will be injected by the metaclass. This information is essential to make the injected attribute visible to mypy.

We can then use this new abstract class as the superclass for any new classes that we define, as follows:

class SomeApplicationClass(Logged):
def __init__(self, v1: int, v2: int) -> None:
self.logger.info("v1=%r, v2=%r", v1, v2)
self.v1 = v1
self.v2 = v2
self.v3 = v1*v2
self.logger.info("product=%r", self.v3)

The __init__() method of the SomeApplication class relies on the logger attribute available in the class definition. The logger attribute was added by the metaclass, with a name based on the class name. No additional initialization or setup overhead is required to ensure that all the subclasses of Logged have loggers available.

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

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