code: def memoize(method):
      
      method.cache = {}
  
      def invalidate(*arguments, **keyword_arguments):
          key = _represent_arguments(*arguments, **keyword_arguments)
          if not key:
              method.cache = {}
          elif key in method.cache:
              del method.cache[key]
          else:
              raise KeyError(
                  'Not prevously cached: %s(%s)' % (method.__name__, key))
  
      def new_method(*arguments, **keyword_arguments):
          """Cache the arguments and return values of the call
  
          The key cached is the repr() of arguments
              This allows more types of values to be used as keys to the cache
              Such as lists and tuples
          """
          key = _represent_arguments(*arguments, **keyword_arguments)
          if key not in method.cache:
              method.cache[key] = method(*arguments, **keyword_arguments)
          return method.cache[key]
      new_method.invalidate = invalidate
      new_method.__doc__ = method.__doc__
      new_method.__name__ = 'memoize(%s)' % method.__name__
      return new_method
docs: A new method which acts like the given method but memoizes arguments
  
      See https://en.wikipedia.org/wiki/Memoization for the general idea
      >>> @memoize
      ... def test(arg):
      ...     print('called')
      ...     return arg + 1
      >>> test(1)
      called
      2
      >>> test(2)
      called
      3
      >>> test(1)
      2
  
      The returned method also has an attached method "invalidate"
          which removes given values from the cache
          Or empties the cache if no values are given
      >>> test.invalidate(2)
      >>> test(1)
      2
      >>> test(2)
      called
      3