Before we talk about efficiency, let's talk about something very important: readability. It took me an unreasonably long time to understand what it was your code was doing, due to lots of single-letter variable names, confusing flow control, and odd choices for functions.
Checking for primality
Let's start with
f is apparently determining the primality of
n, so let's call it
is_prime. Furthermore, it's not actually determining the primality of
n, it's determining the primality of
2n+1. That's too much for one function - let's change it's responsibility to take the number it tests. The way you're doing it (taking the
% against every prime) is a good algorithm, but then you're multiplying the mods together. That doesn't make sense as an operation. You don't care about the product, you care if any of them are zero! And as soon as one of them is zero, you can return
Here's a clearer function:
for p in primes:
if n%p == 0:
Or if you're generator inclined:
return not any(n%p == 0 for p in primes)
We need to find
k primes. You are keeping track of this by comparing
n and occasionally incrementing
k. That is confusing. Since we're already keeping track of all the primes, the termination condition should be:
len(primes) >= k
And then we can handle iteration by the odd numbers with
num_primes = input()
primes = 
def is_prime(n): ...
for n in itertools.count(start=3, step=2):
if len(primes) >= num_primes:
This program is far easier to understand.
We could do better in terms of efficiency. At a first approach, we're checking all the odd numbers but we can drop the multiples of 3 earlier by simply writing our own generator that spits out 3, 5, and then alternates adding 2 and 4. That's a quick optimization that let's us keep the same algorithm.
Better would be to use an entirely different, and very well-known algorithm: the Sieve of Eratosthenes. It is a much better algorithm for this problem.