Applying It to a Logger

Step 5: Applying It to a Logger

The payoff of a reusable decorator is that a brand new class becomes a singleton for free. A shared Logger is a perfect fit: every part of the app should append to the same message list.

The starter decorates a Logger with your singleton from step 4. Implement its two methods:

  • log appends msg to self.messages.
  • all returns a copy of self.messages (a new list, so callers cannot mutate the internal state).

Because Logger is a singleton, every Logger() call returns the same object and shares one messages list. Each test resets that shared list at the top so the tests stay independent within the single process the runner uses.

Hints
Starter code
class Config:
    _instance = None

    def __init__(self):
        self._data = {}

    @classmethod
    def instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    @classmethod
    def reset(cls):
        cls._instance = None

    def set(self, key, value):
        self._data[key] = value

    def get(self, key, default=None):
        return self._data.get(key, default)

    def has(self, key):
        return key in self._data


def singleton(cls):
    instances = {}

    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return get_instance


@singleton
class Database:
    def __init__(self):
        self.connections = 0


@singleton
class Logger:
    def __init__(self):
        self.messages = []

    def log(self, msg):
        """Append msg to the shared message list."""
        # TODO
        pass

    def all(self):
        """Return a copy of all logged messages."""
        # TODO
        pass
Write it on a laptop

The editor and the test runner need a wide screen. Open this page on a desktop browser and your progress will be waiting.