Why is my Sieve of Eratosthenes using generators so slow This is my prime sieve based on the Sieve of Eratosthenes.
import itertools
sieve = itertools.count(2)
primes = []
nprime = 0
for _ in range(10000):
    nprime = next(sieve)
    primes.append(nprime)
    sieve = filter(eval(f&quot;lambda x: x % {nprime} != 0&quot;), sieve)

On my computer this code takes 13.4 seconds to generate 10,000 primes.
That's not the sieve of Eratosthenes
It's rather prime trial division.
Your filter for 2 lets 1/2 of all numbers through, your filter for 3 lets 2/3 of all remaining numbers through, etc. So 1/2 * 2/3 * 4/5 * 6/7 = 22% of all numbers make it through your filters for 2, 3, 5 and 7, so your filter for 11 will have to check 22% of all numbers. The real sieve of Eratosthenes treats 11 by marking off every 11th number, i.e., only 9% of all numbers. Less than half of your percentage. And yours gets worse for the larger primes. For example your filters for the primes from 2 to 89 let ~12% of all numbers through, so your filter for 97 checks ~12% of all numbers. While the real sieve of Eratosthenes treats 97 by marking off only about 1% of all numbers.
So that's why it's slow. It's doing way too much work.
An improvement
You can make it a bit faster by not stacking filters but simply doing the trial divisions at the current number, using the primes you're collecting anyway:
from itertools import count

def primes(n):
    primes = []
    sieve = (x for x in count(2) if all(x % p for p in primes))
    for _ in range(n):
        primes.append(next(sieve))
    return primes

Or using islice:
from itertools import count, islice

def primes(n):
    primes = []
    sieve = (x for x in count(2) if all(x % p for p in primes))
    primes += islice(sieve, n)
    return primes

Demo:
&gt;&gt;&gt; primes(25)
[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]

The real thing
But here's a genuine lazy one based on the paper mentioned by trentcl. I add each prime to the list of its next multiple. When the list of the next x is empty, it's thus a prime. Otherwise, I move the primes from the list to their next multiples.
from itertools import count
from collections import defaultdict

def primes(n):
    marked = defaultdict(list)
    primes = []
    for x in count(2):
        if x in marked:
            for p in marked.pop(x):
                marked[x + p].append(p)
        else:
            primes.append(x)
            if len(primes) == n:
                return primes
            marked[x * x].append(x)

Benchmark 1
Times for n = 10000 (benchmark code at the end of the answer):
9.330 s  original
3.502 s  improved1
3.499 s  improved2
0.065 s  genuine

That said, your original and my improved version can be made much faster by using fewer filters (only up to the square root of the current number), as I've done in my later answer.
To infinity and beyond
As Eric Duminil showed, it's trivial to make the latter an infinite generator, which also makes it shorter. No need to build a result list and check whether you're done, just yield the primes:
infinite:
def primes():
    marked = defaultdict(list)
    for x in count(2):
        if x in marked:
            for p in marked.pop(x):
                marked[x + p].append(p)
        else:
            yield x
            marked[x * x].append(x)

The other solutions can similarly be turned into infinite generators, but meh, they're no good anyway. Plus in my improved1 and improved2, the primes list is not just the result but is also used for filtering, so I need to build it anyway.
As infinite generator, it's more versatile. You can still get a list of the first n if you want to:
&gt;&gt;&gt; list(islice(primes(), 20))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]

You can also get all primes up to a desired limit:
&gt;&gt;&gt; *itertools.takewhile(72 .__gt__, primes()),
(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71)

Or if you neither know how many you need nor how big you need but you need &quot;enough&quot; somehow, for example enough so that their product exceeds a million:
&gt;&gt;&gt; result, product = [], 1
&gt;&gt;&gt; it = primes()
&gt;&gt;&gt; while product &lt; 10**6:
        p = next(it)
        result.append(p)
        product *= p

&gt;&gt;&gt; result
[2, 3, 5, 7, 11, 13, 17, 19]

Or if you just want the millionth prime:
&gt;&gt;&gt; next(islice(primes(), 999999, None))
15485863

Or if you have a prime and want to know the &quot;how manyth&quot; prime it is:
&gt;&gt;&gt; from operator import indexOf
&gt;&gt;&gt; indexOf(primes(), 15485863) + 1
1000000

Or if you have a prime and want to know the next larger one (granted, there are more efficient ways to do that):
&gt;&gt;&gt; it = primes()
&gt;&gt;&gt; 89 in it and next(it)
97

Note that in the last few cases, you don't want a list of primes at all, so building one is wasted memory.
Speaking of memory, though... the generator (and also my version) do take a lot of memory. Here are memory measurements and benchmarks for up to n=1,000,000 for the above &quot;infinite&quot; solution and the solutions I'll show below:
            |     time for the first...    | tracemalloc for 1,000,000
            |  10,000   100,000  1,000,000 |     size         peak
------------+------------------------------+--------------------------
   infinite | 0.068 s   0.971 s   12.450 s |  231,866,275  312,793,599
   no_lists | 0.035 s   0.574 s    8.065 s |  143,870,959  226,554,527
     lazier | 0.033 s   0.456 s    6.016 s |   36,500,174   36,535,742
  recursive | 0.032 s   0.473 s    6.116 s |       72,169      109,109
    twostep | 0.015 s   0.217 s    2.897 s |       71,986      108,926

The memory usage was measured like this:
import tracemalloc
tracemalloc.start()
it = primes()
prime = next(islice(it, 9999999, None))
size, peak = tracemalloc.get_traced_memory()

So the above &quot;infinite&quot; solution takes hundreds of megabytes to generate the first million primes, i.e., hundreds of bytes per prime. Quite a lot. Let's see why, by doing global marked first thing in the primes() function so we can inspect it afterwards. We'll see that almost a million composites are marked and the dictionary takes 84 MB:
&gt;&gt;&gt; it = primes()
&gt;&gt;&gt; next(islice(it, 999999, None))
15485863
&gt;&gt;&gt; from sys import getsizeof
&gt;&gt;&gt; len(marked), getsizeof(marked)
(999919, 83886176)

The lists of primes total 88 MB, and almost all of them contain only a single prime marking the composite:
&gt;&gt;&gt; sum(map(getsizeof, marked.values()))
87992872
&gt;&gt;&gt; from collections import Counter
&gt;&gt;&gt; Counter(map(len, marked.values()))
Counter({1: 999845, 2: 69, 3: 4, 4: 1})

The marked composites take 32 MB and the marking primes take 28 MB:
&gt;&gt;&gt; sum(map(getsizeof, marked))
31983680
&gt;&gt;&gt; from itertools import chain
&gt;&gt;&gt; sum(map(getsizeof, chain.from_iterable(marked.values())))
27999972

Together that's indeed 232 MB as reported by tracemalloc in the table above:
&gt;&gt;&gt; 84 + 88 + 32 + 28
232

Since most lists of marking primes just contain a single prime anyway, let's store only the first prime at the composite. If more primes want to mark that composite, then instead move them ahead to their next multiple:
no_lists:
def primes():
    marked = {}
    for x in count(2):
        if x in marked:
            p = marked.pop(x)
            while (x := x + p) in marked:
                pass
            marked[x] = p
        else:
            yield x
            marked[x * x] = x

As the above table showed, this made it faster and saved a lot of memory.
In my other answer, I showed that prematurely setting marked[x * x] as soon as you discover a new prime x is wasteful. Let's rewrite the better version from there as an infinite generator as well. Here m is the next marking prime, and it will start marking as soon as we reach m2=m2.
lazier:
def primes():
    yield 2
    primes = [2]
    markers = iter(primes)
    marked = {}
    m = next(markers)
    m2 = m * m
    for x in count(3):
        if x == m2:
            while (x := x + m) in marked:
                pass
            marked[x] = m
            m = next(markers)
            m2 = m * m
        elif x in marked:
            p = marked.pop(x)
            while (x := x + p) in marked:
                pass
            marked[x] = p
        else:
            yield x
            primes.append(x)

This brought it down to under 37 MB, as marked got much smaller. However, it creates a primes list again, in order to update m to the next next marking prime.
Can we get the best of both worlds? Not prematurely enter marked[x * x] and not build a primes list? Yes we can!
What do we use the primes list for? Just as a more slowly used stream of primes. But that's what we're writing! A generator of primes. So we can use it recursively. Our main generator iterator of primes will use a slower generator iterator of primes. But where does that slower one get its marker primes from? From yet another even more slowly progressing one, if needed. And so on. A potentially infinite stack of infinite iterators.
recursive:
(also see the addendum at the end of the answer for better versions of this)
def primes():
    yield 2
    marked = {}
    markers = primes()
    m = next(markers)
    m2 = m * m
    for x in count(3):
        if x == m2:
            while (x := x + m) in marked:
                pass
            marked[x] = m
            m = next(markers)
            m2 = m * m
        elif x in marked:
            p = marked.pop(x)
            while (x := x + p) in marked:
                pass
            marked[x] = p
        else:
            yield x

Let's see the levels by storing each iterator's marked in a global levels list by doing levels.append(marked) right after marked = {}. Then:
&gt;&gt;&gt; levels = []
&gt;&gt;&gt; millionth = next(islice(primes(), 999999, None))
&gt;&gt;&gt; for marked in levels:
        print(len(marked), max(marked.values()))

546 3931
18 61
4 7
2 3
1 2

We see that there are five levels of iterators. The top-level iterator, the one we're using directly, has 546 marking primes, the largest being 3931. That makes sense, as the next larger prime is 3943, which starts marking at 39432, which is larger than the millionth prime:
&gt;&gt;&gt; it = primes()
&gt;&gt;&gt; 3931 in it and next(it)
3943
&gt;&gt;&gt; 3943**2
15547249
&gt;&gt;&gt; millionth
15485863

The next-lower level so far only needed to yield the 18 primes up to 61. Which is correct, as the next-larger prime 67 will only start marking at 672=4489, well above the prime 3943 needed next by the top-level iterator. And so on, until the fifth and lowest level only needed its initial yield 2 so far.
With only a few hundred primes in all levels combined, it's no wonder this solution takes faaar less memory than our first &quot;infinite&quot; one, about 3000 times less, only around 100 KB. And its advantage will increase for larger numbers of primes, as the earlier solutions need O(n) memory while the recursive ones take something like O(sqrt(n)) memory (not sure how the density of primes affects it).
Last but not least, here's a little variation where I handle the prime 2 separately and then move everything twice as fast, which makes the overall solution about twice as fast (again see the table above):
twostep:
def primes():
    yield 2
    yield 3
    marked = {}
    markers = primes()
    next(markers)
    m = next(markers)
    m2 = m * m
    for x in count(5, 2):
        if x == m2:
            marked[m2 + 2*m] = 2*m
            m = next(markers)
            m2 = m * m
        elif x in marked:
            step = marked.pop(x)
            while (x := x + step) in marked:
                pass
            marked[x] = step
        else:
            yield x

Code for benchmark 1:
from timeit import repeat
from itertools import count, islice
from collections import defaultdict

def original(n):
    sieve = count(2)
    primes = []
    nprime = 0
    for _ in range(n):
        nprime = next(sieve)
        primes.append(nprime)
        sieve = filter(eval(f&quot;lambda x: x % {nprime} != 0&quot;), sieve)
    return primes

def improved1(n):
    primes = []
    sieve = (x for x in count(2) if all(x % p for p in primes))
    for _ in range(n):
        primes.append(next(sieve))
    return primes

def improved2(n):
    primes = []
    sieve = (x for x in count(2) if all(x % p for p in primes))
    primes += islice(sieve, n)
    return primes

def genuine(n):
    marked = defaultdict(list)
    primes = []
    for x in count(2):
        if x in marked:
            for p in marked.pop(x):
                marked[x + p].append(p)
        else:
            primes.append(x)
            if len(primes) == n:
                return primes
            marked[x * x].append(x)

funcs = original, improved1, improved2, genuine

n = 10000

expect = funcs[0](n)
for func in funcs[1:]:
    print(func(n) == expect, func.__name__)

for _ in range(3):
    for func in funcs:
        t = min(repeat(lambda: func(n), number=1))
        print('%.3f s ' % t, func.__name__)
    print()

Addendum - better recursive generators
I came up with a nicer way later, don't want to rewrite the answer using this now but want to share. This goes through the primes of the underlying generator in an outer loop and from one prime's square to the next prime's square in an inner loop. It's less code and makes the structure of the procedure clearer.
def primes():
    yield 2
    marks = {}
    i = 2
    for p in primes():
        for i in range(i+1, p*p):
            if i not in marks:
                yield i
            else:
                m = marks.pop(i)
                j = i + m
                while j in marks:
                    j += m
                marks[j] = m
        marks[p*p] = p

And a version with the paper's way of potentially storing several primes at the same common multiple:
def primes():
    yield 2
    marks = defaultdict(set)
    i = 2
    for p in primes():
        for i in range(i+1, p*p):
            if i not in marks:
                yield i
            else:
                for m in marks.pop(i):
                    marks[i+m].add(m)
        marks[p*p] = {p}