import math

def caching(func):
    cache = {}
    def wrapped_func(*args):
        argstr = '({})'.format(', '.join(map(str, args)))
        if args not in cache:
            print(f'> calculating {func.__name__}{argstr}')
            cache[args] = func(*args)
        else:
            print(f'> reusing cached result for {func.__name__}{argstr}')
        return cache[args]
    return wrapped_func

@caching
def my_multiply(x, y):
    return x * y

@caching
def my_divide(x, y):
    return x / y

@caching
def my_hypot(a, b):
    return math.sqrt(a * a + b * b)

print(my_multiply(2, 2))
print(my_divide(2, 2))
print(my_multiply(2, 2))
print(my_divide(2, 2))
print(my_hypot(2, 3))
print(my_divide(2, 3))
print(my_hypot(2, 3))
print(my_hypot(3, 3))