I built an algorithm (sieve of Eratosthenes) for finding primes, but it consumes a lot of memory.  Currently, my code uses a decorator to monitor the time eclipsed.  Could you come up with a similar decorator to evaluate the memory consumed by my program?
    import math
    import time
    def time_usage(func):
        def wrapper(*args, **kwargs):
            beg_ts = time.time()
            result = func(*args, **kwargs)
            end_ts = time.time()
            print("[INFO] elapsed time: %f" % (end_ts - beg_ts))
            return result
        return wrapper
    @time_usage
    def find_prime(n):
        is_composite = [False for _ in range(n + 1)]
        # Cross out multiples of 2
        for i in range(4, n, 2):
            is_composite[i] = True
        # Cross out multiples of primes found so far
        next_prime = 3
        stop_at = math.sqrt(n)
        while next_prime < stop_at:
            # Cross out multiples of this prime
            for i in range(next_prime * 2, n, next_prime):
                is_composite[i] = True
            # Move the next prime, skipping the even number
            next_prime += 2
            while next_prime <= n and is_composite[next_prime]:
                next_prime += 2
        # Copy the primes into a list
        primes = []
        for i in range(2, n):
            if not is_composite[i]:
                primes.append(i)
        return primes
    if __name__ == '__main__':
        print(find_prime(100000))
One suggestion is to use a third party library to profile the memory usage. I used the memory_profiler as it offers a nice decorator implementation however I cannot use both time_usage decorator and the memory profile.
Here I can see that @profile is actually profiling the memory of time_usage.
import math
import time
from memory_profiler import profile
def time_usage(func):
    def wrapper(*args, **kwargs):
        beg_ts = time.time()
        result = func(*args, **kwargs)
        end_ts = time.time()
        print("[INFO] elapsed time: %f" % (end_ts - beg_ts))
        return result
    return wrapper
@profile
@time_usage
def find_prime(n):
    is_composite = [False for _ in range(n + 1)]
    # Cross out multiples of 2
    for i in range(4, n, 2):
        is_composite[i] = True
    # Cross out multiples of primes found so far
    next_prime = 3
    stop_at = math.sqrt(n)
    while next_prime < stop_at:
        # Cross out multiples of this prime
        for i in range(next_prime * 2, n, next_prime):
            is_composite[i] = True
        # Move the next prime, skipping the even number
        next_prime += 2
        while next_prime <= n and is_composite[next_prime]:
            next_prime += 2
    # Copy the primes into a list
    primes = []
    for i in range(2, n):
        if not is_composite[i]:
            primes.append(i)
    return primes
if __name__ == '__main__':
    print(find_prime(100000))
Produce this :
Line # Mem usage Increment Line Contents
7 27.4 MiB 0.0 MiB def wrapper(*args, **kwargs): 8 27.4 MiB 0.0 MiB beg_ts = time.time() 9 28.3 MiB 0.9 MiB result = func(*args, **kwargs) 10 28.3 MiB 0.0 MiB end_ts = time.time() 11 28.3 MiB 0.0 MiB print("[INFO] elapsed time: %f" % (end_ts - beg_ts)) 12 28.3 MiB 0.0 MiB return result[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, ..., 99989, 99991]
 
     
    