Context Managers and Cross Cutting concerns in Python

Hello,

In this short article I would like to talk about context managers. I personally consider that at the core they are just a form of decorators. If you don’t know what a decorator is check the Decorator Pattern Wikipedia article.

Decorators can be used to implement cross-cutting concerns. We have componentA and we need logging and security, we could write the logic for logging and security handling in componentA but some people consider component a should be componentA not componentAthatAlsoKnowsAboutSecurityAndOtherStuff. Since it’s not the component’s responsibility to authorize requests or log calls to a external logging service, we can wrap the componentA into a decorator that does just that.

A formal definition for cross-cutting concerns as taken from Wikipedia is the following:

In aspect-oriented software development, cross-cutting concerns are aspects of a program that affect other concerns. These concerns often cannot be cleanly decomposed from the rest of the system in both the design and implementation, and can result in either scattering (code duplication), tangling (significant dependencies between systems), or both.

And some examples of cross cutting concerns include:

Since the context managers are sort of similar to decorators you can use them to implement cross cutting concerns. Let’s explore.

Simple Example

In Python you can have two types of context managers: a function and a class. In order for the function to behave like a context manager it will need to be decorated with the @contextmanager decorator, and in order for a class behave like a context manager it needs to implement __enter__ and __exit__.

Context managers can be called using the with statement. The following code snippet demonstrates two context managers:

  • One that logs when the function is called and when it exits.
  • One that intercepts the function arguments.
from contextlib import contextmanager

@contextmanager
def simple_context_manager(function):
    try:
        print("calling function")
        yield function
    finally:
        print("function call has ended")

class SimpleContextManager:
    def __init__(self, cb):
        self.cb = cb

    def _intercept(self, *args, **kwargs):
        print(f"calling with {args} {kwargs}")
        return print(*args, **kwargs)

    def __enter__(self):
        print("intercept start")
        return self._intercept

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("intercept end")

def main():
    with simple_context_manager(print) as print_func:
        print_func("hi")

    with SimpleContextManager(print) as print_func:
        print_func("hi")
        print_func("hi", end="\n\n", sep=",")
        print_func("hi")

if __name__ == '__main__':
    main()

Caching

What is caching? In short..

Caching is used to store the result of an expensive computation somewhere in memory or on a persistent storage device in order to optimize the program.

We have the compute_fibonacci function, which is quite slow. A version that uses cache has been implementing in the CachedComputeFibonacci class. Notice how the code takes some time to output the result for the first call of print(cached_compute_fibonacci(35)) statement but the second print in instant.

def compute_fibonacci(number):
    if number <= 1:
        return number
    return compute_fibonacci(number-1) + compute_fibonacci(number-2)


class CachedComputeFibonacci:
    def __init__(self):
        self._cache = {}

    def __call__(self, *args, **kwargs):
        number = args[0]
        if number in self._cache:
            return self._cache[number]
        result = compute_fibonacci(number)
        self._cache[number] = result
        return result

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

def main():
    # Non cached
    print(compute_fibonacci(10))

    # Cached
    with CachedComputeFibonacci() as cached_compute_fibonacci:
        print(cached_compute_fibonacci(35))
        print(cached_compute_fibonacci(35))



if __name__ == '__main__':
    main()

Logging

Logging can be useful for debugging and auditing purposes.

def compute_fibonacci(number):
    if number <= 1:
        return number
    return compute_fibonacci(number-1) + compute_fibonacci(number-2)


class LoggedComputeFibonacci:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        print(f"calling compute_fibonacci with args={args} kwargs={kwargs}")
        result = compute_fibonacci(args[0])
        print(f"compute_fibonacci={result}")
        return result

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

def main():
    # Logging
    with LoggedComputeFibonacci() as cached_compute_fibonacci:
        print(cached_compute_fibonacci(35))
        print(cached_compute_fibonacci(36))



if __name__ == '__main__':
    main()

Error detection and correction

If you find yourself duplicating the same try/catch logic in multiple places of your code perhaps you can extract it into a context manager for handling errors:

from contextlib import contextmanager

@contextmanager
def my_error_handler():
    try:
        yield
    except ZeroDivisionError:
        print("abort abort")

def main():
    # error handling
    with my_error_handler():
        print("0 / 0 =", 0 / 0)



if __name__ == '__main__':
    main()

The code is definitely more cleaner this way, in my opinion.

Thanks for reading and I hope that you’ve learnt something!

Method Injection and Property Injection Design Patterns

Hello,

In this article we’re going to explore the Method Injection and Property Injection design patterns.

To demonstrate the patterns I’m going to add a new interface named Encoder to the printer.py file and a concrete implementation for two encoders: Rot13Encoder and NullEncoder.

class Encoder(metaclass=abc.ABCMeta):
    def encode(self, message: Message) -> Message:
        raise NotImplementedError("encode must be implemented!")


class Rot13Encoder(metaclass=abc.ABCMeta):
    def encode(self, message: Message) -> Message:
        return Message(codecs.encode(str(message), 'rot_13'))


class NullEncoder(metaclass=abc.ABCMeta):
    def encode(self, message: Message) -> Message:
        return message

The Encoder will be used by the printer in order to encode the messages before printing them.

Method Injection

The method injection pattern is used as an alternative to the constructor injection when the dependency is optional or is only used in one spot, so it wouldn’t make sense to inject it in the constructor.

My console printer would look like this If I’d use this pattern:

class ConsolePrinter(Printer):
    def __init__(self, prefix: str):
        self._prefix = prefix

    def print(self, message: Message, encoder: Encoder):
        print(self._prefix, encoder.encode(message))

When the application.py would call Printer.print it would pass the Encoder as a dependency.

Property Injection

The property injection patter is mostly used in libraries, applications should avoid it. To use the property injection pattern I would have to modify the ConsolePrinter class like so:

class ConsolePrinter(Printer):
    def __init__(self, prefix: str):
        self._prefix = prefix
        self.encoder = NullEncoder()

    def print(self, message: Message):
        print(self._prefix, self.encoder.encode(message))

I have a property called encoder which by default acts as a NullEncoder, if for some reason the user of the library needs to change it, it can do so by injecting the needed dependency in the property.

The code for the Property Injection and Method Injection patterns is on my Github! πŸ™‚

Thanks for reading!

Constructor Injection and Null Object Design Patterns

The Constructor Injection design pattern is a pattern that helps you declare all the required dependencies of a class in it’s constructor.

This is useful because it helps you decouple the code, you can specify an interface instead of a concrete type, remember, program to an interface.

Also, in the constructor it is easier to guard against null objects. The calling code doesn’t have to worry about null exceptions every time it uses a dependency.

Avoid providing defaults when using this pattern, as this will couple the code with a concrete type. When a dependency is not needed, use the Null Object pattern.

We’re going to pickup from the last article and show you how you can modify the application to use the constructor injection and null object design patterns.

The class graph will look like this:

In order to demonstrate this pattern I will introduce a new class MessageTranslator.

class MessageTranslator:
    def __init__(self, translator: Translator, printer: Printer):
        if not translator:
            raise ValueError("Translator cannot be None.")
        if not printer:
            raise ValueError("Printer cannot be None.")

        self._translator = translator
        self._printer = printer

    def translate(self, message):
        return self._translator.translate(message)

    def print(self, message):
        self._printer.print(message)

And modify the Application code to use it:

class Application:
    def __init__(self):
        self._input_listener: InputListener = ConsoleInputListener("< ")

    def start(self):
        print("starting application.")
        message_translator = MessageTranslator(RomanianTranslator(), ConsolePrinter(">"))
        while True:
            user_in = Message(self._input_listener.get_input())
            if str(user_in) == "exit":
                exit(0)

            translated_message = message_translator.translate(user_in)
            message_translator.print(translated_message)

That’s it! You’ve used the constructor injection pattern.

Now, if don’t want to print the translated message into the console we can’t just pass a null Printer, that would raise an exception.

We need use the null object pattern to implement a Printer that does nothing.

class VoidPrinter(Printer):
    def print(self, message):
        pass

If we modify the Application code to use our VoidPrinter, the output would be:

starting application.
 < hello Dev
 < 

Thanks for reading! As always, the full code can be found on my Github.

Composition Root Pattern: How to Write Modular Software

The composition root is a design pattern which helps you structure a software application by implementing a class that builds all the other classes.

In this example we will examine this pattern in Python.

Here’s the object graph of the classes that we’re going to implement:

I have designed a sample application that we’re going to use. It contains three components: ConsoleInputListener, ConsolePrinter and RomanianTranslator and a value object class: Message.

The classes are described as follows:

  • Application: The composition root, glues all the classes together.
  • ConsoleInputListener: Component, it reads string from the standard input.
  • ConsolePrinter: Component, it prints to the standard output.
  • RomanianTranslator: Component, it translates English words to Romanian.
  • Message: Value object, it encapsulates the message string.

Program to an interface not an implementation

Before implementing the Application component, I’m going to define the interfaces for the ConsoleInputListener, ConsolePrinter and RomanianTranslator. I’m going to call them InputListener, Printer and Translator for simplicity.

The reason I’m defining interfaces* is because I want to be able to swap the objects that the Application class references. In Python variables don’t constrain me to any type, but if I’m going to implement other objects, I’d like to have a template so it will help me reduce the number of mistakes that I can make.

Python doesn’t have support for interfaces so I’m going to use abstract classes:

class Printer(metaclass=abc.ABCMeta):
    def print(self, message):
        raise NotImplementedError("print is not implemented")

class InputListener(metaclass=abc.ABCMeta):
    def get_input(self) -> str:
        raise NotImplementedError("get_input is not implemented!")

class Translator(metaclass=abc.ABCMeta):
    def translate(self, message: Message) -> Message:
        raise NotImplementedError("translate must be implemented!")

Every class that extends my abstract classes must implement it’s abstract methods:

class ConsolePrinter(Printer):
    def __init__(self, prefix: str):
        self._prefix = prefix

    def print(self, message: Message):
        print(self._prefix, message)

class ConsoleInputListener(InputListener):
    def __init__(self, prompt: str):
        self._prompt = prompt

    def get_input(self) -> str:
        return input(self._prompt)

class RomanianTranslator(Translator):
    def translate(self, message: Message) -> Message:
        words_map = {"hello": "salut"}
        message_words = str(message).split(" ")

        for index, word in enumerate(message_words):
            if word.lower() in words_map.keys():
                message_words[index] = words_map[word]

        return Message(" ".join(message_words))

The Message class, for the sake of completeness only holds a string.

class Message:
    def __init__(self, message):
        self._message = message

    def __str__(self):
        return self._message

And finally, the Application class will glue all the components together and instantiate them:

from input_listener import InputListener, ConsoleInputListener
from message import Message
from printer import Printer, ConsolePrinter
from translator import Translator, RomanianTranslator


class Application:
    def __init__(self):
        self._printer: Printer = ConsolePrinter(">")
        self._translator: Translator = RomanianTranslator()
        self._input_listener: InputListener = ConsoleInputListener("< ")

    def start(self):
        print("starting application.")
        while True:
            user_in = Message(self._input_listener.get_input())
            if str(user_in) == "exit":
                exit(0)

            self._printer.print(self._translator.translate(user_in))

The main method will just run the Application:

from application import Application


def main():
    app = Application()
    app.start()


if __name__ == '__main__':
    main()

Running the application would output:

 starting application.
 < hello Denis!
 > salut Denis! 

Now, most real world applications aren’t this simple, the reason we went through all this code in order to implement a simple hello world is the following: Imagine that you have 10 translators: English, French, German… and two Printers: Console and File.

You could modify the application code to take in two parameters: translator and printer use the arguments to instantiate the correct translator and printer without needing to change the other classes. You can add as many printers, translators and input listeners as you wish. That’s a huge benefit.

If you were to inline all the code in a single class, adding more translations and more printing options would have been very painful and frustrating.

I hope my article gave you some insight into the Composition Root pattern, if I made any mistake please feel free to correct me. πŸ™‚

The full code is on my Github.

Thanks for reading!