I have written a simple snippet of code that implements a function that returns a list of primes up to a certain integer n
. Assuming that this function could be called several times in the script for different values of n
, I wanted to make it as "lazy" as possible—that is, able to reuse previously-calculated variables.
Here is the code (assume isPrime(n)
returns True
if n
is prime and False
otherwise):
primes = [] def pi(n): global primes if len(primes) == 0: # Full for i in range(2, n + 1): if isPrime(i): primes.append(i) return primes elif n <= primes[-1]: # Lazy return list(filter(lambda m: m <= n, primes)) else: # Semi-lazy for i in range(primes[-1] + 1, n + 1): if isPrime(i): primes.append(i) return primes
The problem is, as you may have spotted, that I'm using a global variable (primes
) to store the list of primes, and later modifying it inside the pi
function. I've heard that relying on mutable global variables is a bad design choice. What are better ways to implement this?
This question should actually be generalized to the following: what's the best design pattern to implement a 'lazy' function (i.e. a function that reuses previously calculated values)?
list(filter(lambda...))
can be inefficient too. Consider generating a few hundred million primes, and then asking for the list of primes up to 100. When thefilter()
reaches101
, it could stop, but won’t since it doesn’t know that.itertools.takewhile()
would be an improvement. Better still would bebisect
to find the endpoint, and then simply returning a slice.\$\endgroup\$filter
but didn't know/hadn't made research about better-fitting alternatives when quickly putting this together. I think in this case I'd usetakewhile
; it seems more 'human-readable' and straightforward to me.\$\endgroup\$return primes[:bisect.bisect(primes, n)]
. Just so you know.\$\endgroup\$