You have an interesting optimization in your code: you use the fact that the first prime number is 2 to skip all tests of this value, and increment each test by 2.
Call this fact out in a comment!
I agree with SuperBiasedMan that it is potentially confusing, and should be called out. One place it is confusing is in the while loop comparison, where you have to write < (n - 1). It's usually easier to read comparisons when the actual number is in the comparison, whether that means changing from a less than to a less than or equal, or changing the initialization as in this example. Add the value to your list by initializing it with primes = [2] or, if you want to be verbose:
primes = [] # Initialize the algorithm with the first prime primes.append[2] # All other primes are not divisible by previous entries in 'primes' collection ...algorithm...
But, if you do this, every future loop starts out by testing 2, which is a performance hit that you worked around by incrementing by 2. Either start your loop on the second iteration, or don't add it to the list.
Especially in mathematics problems like this, there is always a trade-off between speed and readability. Isolate it from the users of your code, and only optimize when actually necessary, but make it a conscious decision. Usually, business logic is whatever's clearer, and back-end algorithms like this is whatever's fastest.
There are many prime generation algorithms you can use (which is another code review bit of advice: use existing libraries and research!) or you can optimize what you have. For starters, you don't need to check the whole list - just until the factor being tested is greater than or equal to the square root of your current potential prime candidate. 13 will not be a factor of 14, you only need to check up to 3. But is it faster? Square root is expensive on some hardware. Maybe use a square root approxomation. You'll need benchmarks. And now why is there a square root in your prime verification? It will need a comment!