The most important thing here is to remove the quadratic behavior. You have this for two reasons.
First, the remove call searches the entire list of values ββto remove. This takes linear time, and you do it once for each element in removable , so your total time is O(NM) (where N is the length of the primes and M is the length of the removable ).
Secondly, removing items from the middle of the list forces you to move the rest of the list into one slot. So, each of them takes linear time, and again you do it M times, so again it is O(NM) .
How can you avoid this?
For the first, you need to either use sorting, or just use something that allows you to search by constant time instead of linear time, such as set .
For the second, you need to create a list of indexes to delete, and then perform a second pass to move each item up the corresponding number of indexes at the same time, or simply create a new list instead of trying to change the original in place.
So, there are many options. Which one is better? It almost certainly does not matter; changing your O(NM) to O(N+M) is likely to be more than sufficient for the optimization you are pleased with the results. But if you need to squeeze more performance, you will have to implement them all and test based on realistic data.
The only one that I think is not obvious is how to "use sorting". The idea is to use the same iterations with zip steps that you would use in a merge sort, for example:
def sorted_subtract(seq1, seq2): i1, i2 = 0, 0 while i1 < len(seq1): if seq1[i1] != seq2[i2]: i2 += 1 if i2 == len(seq2): yield from seq1[i1:] return else: yield seq1[i1] i1 += 1