I Reviewed 500+ Pull Requests — Here’s What Every Python Dev Gets Wrong

After reviewing hundreds of PRs, patterns start to emerge. And some of them are surprisingly consistent — even among experienced Python…

I Reviewed 500+ Pull Requests — Here’s What Every Python Dev Gets Wrong
Photo by Sigmund on Unsplash

They Thought They Nailed It — They Didn’t

I Reviewed 500+ Pull Requests — Here’s What Every Python Dev Gets Wrong

After reviewing hundreds of PRs, patterns start to emerge. And some of them are surprisingly consistent — even among experienced Python developers. Here’s what I’ve seen, and what you can do better.

The Invisible Code Smells You Might Be Shipping

I’ve been reviewing pull requests almost daily for the past few years — across startups, open-source projects, and client work. Python is often the common denominator. It’s a beautiful language, sure, but it also makes it dangerously easy to write poor-quality code that looks fine at first glance.

After combing through 500+ PRs, I noticed something alarming: even skilled Python developers repeatedly make the same subtle (but critical) mistakes. These aren’t just beginner oversights — they’re bad habits, misused language features, and readability killers that sneak past linters and CI pipelines.

So if you’re pushing Python code, this one’s for you.


1. Misusing Default Mutable Arguments

This one’s a classic, and it’s still everywhere.

def append_item(item, my_list=[]): 
    my_list.append(item) 
    return my_list

What’s wrong? That [] is a trap. It gets evaluated once at function definition—not every time the function is called. So the list keeps growing across calls, unintentionally.

The fix:

def append_item(item, my_list=None): 
    if my_list is None: 
        my_list = [] 
    my_list.append(item) 
    return my_list

If you’re still doing the first one, it’s time to stop.

It’s not just a bug — it’s a ticking time bomb in your API.

2. Writing Code That’s “Too Pythonic”

There’s a fine line between writing clean idiomatic code and overdoing the Pythonic one-liners.

Take this:

result = [func(x) for x in items if condition(x) and other_check(x) and yet_another(x)]

Or worse:

value = next((x for x in items if x > 10 and x % 2 == 0), None)

Sure, it’s compact. But is it readable? Maintainable? Debbugable? That’s where most PRs fail — not in correctness, but in empathy for the next developer.

Remember: Just because you can doesn’t mean you should.

3. Reinventing the Standard Library

Python’s standard library is a treasure trove. But I still see PRs with code like this:

def flatten(list_of_lists): 
    return [item for sublist in list_of_lists for item in sublist]

Looks clever. But why not use:

import itertools 
flattened = list(itertools.chain.from_iterable(list_of_lists))

Or re-implementing Counter, defaultdict, namedtuple, Pathlib logic—you name it.

If you’re writing more than 5 lines to do something common, check the standard library first.

4. Abusing try-except as Flow Control

I get it — Python makes exceptions easy. But I’ve seen this far too often:

try: 
    value = my_dict['key'] 
except KeyError: 
    value = 'default'

Or even:

try: 
    risky_call() 
except: 
    pass  # 😱

This is not just lazy — it’s dangerous.

Better:

value = my_dict.get('key', 'default')

Or for errors:

try: 
    risky_call() 
except SpecificError as e: 
    logger.error("Handled error: %s", e)

Catching everything is not resilience — it’s recklessness.

5. Not Writing Enough Tests (Or the Right Ones)

You’d be shocked how many PRs introduce new logic without a single test. Even worse? Tests that only cover the happy path.

Good PRs include:

  • Unit tests for all new functions/classes
  • Edge case coverage
  • Clear setup and teardown logic
  • No dependency on external state

If your test passes but breaks when someone changes one line of unrelated code, it’s not a real test.

6. Poor Naming and Inconsistent Style

This one is subtle but brutal for team velocity.

Here’s what I often see:

  • Variables named data, info, or temp
  • Mixed camelCase and snake_case in the same file
  • Function names like do_process() or handle_stuff()

Code is read more than it’s written. Every bad name is a tax on your teammates.

Fix it by:

  • Following PEP8 religiously
  • Naming things based on intent, not implementation
  • Using linters and formatters (black, flake8, isort) as pre-commit hooks

You don’t get points for clever naming. You get points for clarity.

7. Ignoring Async — Even When It’s Needed

Many developers still write blocking I/O code in async-capable services like FastAPI or asyncio projects:

def fetch_data(): 
    response = requests.get("https://api.example.com") 
    return response.json()

This blocks the event loop! Instead:

import httpx 
 
async def fetch_data(): 
    async with httpx.AsyncClient() as client: 
        response = await client.get("https://api.example.com") 
        return response.json()

If you’re working in an async context — use async libraries. Mixing sync and async is a silent performance killer.

8. Over-Engineering for “Future-Proofing”

I’ve reviewed PRs with:

  • Abstract base classes no one needs
  • Config layers with no configs
  • Factories and services for a 50-line script

Ask yourself:

  • Is this solving a real problem now?
  • Will this code still make sense to a new hire in 3 months?

Write for today. Refactor when tomorrow actually comes.

So, What Makes a Great Python PR?

Here’s a mini checklist you can use before clicking “Create Pull Request”:

  • Does the code follow PEP8 and use linters/formatters?
  • Are function names and variables self-explanatory?
  • Are edge cases handled clearly — not just caught silently?
  • Is the solution readable and boring in the best way?
  • Are there sufficient (and meaningful) tests?
  • Could this logic be simplified using a built-in or standard library?

If you answer “no” to any of the above — revisit your PR. Future-you will thank you.


Final Thoughts

Python’s simplicity is both its greatest strength and most dangerous weakness. It’s easy to write something that works — but writing code that’s readable, maintainable, and truly “Pythonic” is a skill honed with care.

After reviewing 500+ PRs, I’ve learned this:
Great Python code isn’t clever. It’s clear.

So the next time you submit a pull request, don’t just ask:
Does it run?
Ask: Will anyone want to read this tomorrow?

Photo by Kevin Canlas on Unsplash