Solution 1

class Calculator(object):
    def __init__(self):
        self.cache = {}

    def _find_cached_computation(self, operation, params):
        if operation not in self.cache:
            return None
        for prev_computations in self.cache[operation]:
            if prev_computations[:2] == params:
                return prev_computations[2]

    def _append_to_cache(self, operation, cached_value):
        self.cache.setdefault(operation, [])
        self.cache[operation].append(cached_value)

    def add(self, x, y):
        cached = self._find_cached_computation('add', (x, y))
        if not cached:
            cached = x + y
            self._append_to_cache('add', (x, y, cached))
        return cached

    def subtract(self, x, y):
        cached = self._find_cached_computation('subtract', (x, y))
        if not cached:
            cached = x - y
            self._append_to_cache('subtract', (x, y, cached))
        return cached

Cached Calculator

Write a class Calculator that has the same methods as the previous one (add and subtract). But it should also have an attribute cache that keeps track of the operations being already performed as a dictionary (see example below and test cases). If the operation was already performed, the method should return the value from the cache. Example:

>>> c = Calculator()
>>> c.cache
{}
>>> c.add(2, 3)
5
>>> c.cache
{
  'add': [
    (2, 3, 5)
  ]
}
>>> c.subtract(8, 2)
6
>>> c.cache
{
  'add': [
    (2, 3, 5)
  ],
  'subtract': [
    (8, 2, 6)
  ]
}
>>> c.add(9, 3)
12
>>> c.cache
{
  'add': [
    (2, 3, 5),
    (9, 3, 12)
  ],
  'subtract': [
    (8, 2, 6)
  ]
}
# Same method invoked again:
>>> c.add(2, 3)  # Should be returned from the cache
5

Test Cases

test calculator cache - Run Test

def test_calculator_cache():
    c = Calculator()
    assert hasattr(c, 'cache')
    assert c.cache == {}

    assert c.add(2, 8) == 10
    assert c.cache == {
        'add': [
            (2, 8, 10)
        ]
    }

    assert c.subtract(7, 2) == 5
    assert c.cache == {
        'add': [
            (2, 8, 10)
        ],
        'subtract': [
            (7, 2, 5)
        ]
    }

    assert c.subtract(11, 7) == 4
    assert c.cache == {
        'add': [
            (2, 8, 10)
        ],
        'subtract': [
            (7, 2, 5),
            (11, 7, 4)
        ]
    }

    # Repeated operation. Should be cached
    assert c.add(2, 8) == 10
    assert c.cache == {
        'add': [
            (2, 8, 10)
        ],
        'subtract': [
            (7, 2, 5),
            (11, 7, 4)
        ]
    }