The patterns that make code unpythonic
Most "unpythonic" code isn't wrong — it works. It's just unnecessary indirection, defensive coding that doesn't earn its weight, or imports of styles from other languages. Recognizing these patterns and pruning them makes code shorter, faster to read, and easier to maintain.
1. range(len(seq)) — when you really want enumerate
If you're iterating for i in range(len(seq)) just to access seq[i], you almost certainly want for i, x in enumerate(seq). The exception: when you genuinely don't need the value (just the index, for some computation). Even then, ask yourself why.
2. Using == to compare with None
x == None works but isn't the idiom. Use x is None / x is not None. None is a singleton; identity comparison is faster and reads better. Same applies to True and False in the rare cases you'd compare against them at all.
3. Verbose dict iteration
for key in d.keys(): when you're not using key.foo() on the key view: just for key in d:. for key in d.keys(): print(d[key]): just for value in d.values():. for key in d.keys(): print(key, d[key]): just for key, value in d.items():.
4. Default mutable arguments
def f(x=[]): covered repeatedly. The shared-default-list bug. Use def f(x=None): if x is None: x = []. Memorize it; the bug is subtle and easy to introduce.
5. Catching too broadly
except: catches BaseException including KeyboardInterrupt. except Exception: is broad but acceptable. Better: catch the specific exception types you can recover from. Other exceptions should propagate.
6. Optimizing without measuring
Premature optimization is the bug-introducing kind. Profile (tooling track) first; optimize where the time actually is. Don't skip arrays, write C extensions, or vectorize before measuring — you'll spend hours on code that wasn't slow.
range(len(seq)) is reasonable when indices are the actual data. Let the pattern trigger a question, then preserve the clearest intent.