Python mistakes I see people make repeatedly

There is no official document called a Field Guide For Python Common Mistakes To Avoid. What exists are scattered blog posts, issue trackers, and the collective frustration of developers who have spent years debugging the same problems. I started keeping my own notes after my third production incident involving mutable default arguments. That was four years ago. The list has grown since then, and some entries have been revised when I realized my original understanding was incomplete. These two issues are related in a way most tutorials don't explain clearly enough. A mutable default argument in Python gets evaluated once at function definition time, not at call time. So when you write def append_item(item, lst=[]):, that empty list object is created once and reused across every call where lst isn't provided. It accumulates values unexpectedly. The standard workaround is using None as the sentinel and creating the list inside the function body. The closure problem is trickier because it hides in plain sight. Consider this pattern used constantly in beginner code:

functions = [] for i in range(3): functions.append(lambda: i) All three lambdas return 2, not 0, 1, 2 By the time any of those lambdas executes, the loop has finished and i equals 2. The closure captures the variable, not the value. The fix involves binding the current value at definition time. You can do this with a default argument on the lambda itself: lambda i=i: i. Or you can use functools.partial. I use the default argument approach because it requires no imports and shows intent immediately when someone reads the code. I ran into a particularly ugly version of this bug in a data processing pipeline where twelve lambda functions were supposed to each filter a different date range. They all filtered by the last date instead. It took me two hours to trace because the code looked structurally correct. The runtime behavior was the only thing wrong.

Understanding scope resolution without overcomplicating it

Python uses LEGB resolution: Local, Enclosing, Global, Built-in. Most people learn this and then immediately forget how it actually behaves in practice. The enclosing scope rule is where things get weird. If you read a variable from an outer function, Python finds it fine. If you try to assign to it, Python creates a new local variable instead and throws an UnboundLocalError if you reference it before the assignment. The nonlocal keyword exists for this exact reason, but it only works in nested function definitions. It does not reach into module-level globals. For that you need the global keyword, though relying on it is usually a sign your architecture has become too tangled. I've seen codebases where half the functions modify global state, making them impossible to test in isolation. There is a counter-intuitive edge case worth noting. If a function contains any assignment to a variable name anywhere in its body, Python treats that name as local throughout the entire function, even in lines before the assignment. This means this code crashes:

Get the Full Details

Common Python Programming Mistakes to Avoid | How to avoid python mistakes, Python coding tips ...
Common Python Programming Mistakes to Avoid | How to avoid python mistakes, Python coding tips ...

def example(): print(x) x = 5 Even though the NameError would not occur if x were defined globally, Python's parser sees the assignment and marks x as local before the function runs. The error message says "local variable 'x' referenced before assignment" which can be misleading if you expected it to use the global. This behavior is by design but absolutely catches people off guard.

String formatting choices matter more than you think

Python has had multiple string formatting methods for decades. The old percent-style formatting, str.format(), and f-strings. Each has legitimate use cases, but the ecosystem has largely settled on f-strings for general purposes. The reason isn't just style preference. F-strings are measurably faster than both % formatting and .format() in tight loops, and the performance gap widens with complexity. Here is what most people don't realize: f-strings evaluate their expressions at runtime, which means you can embed arbitrary Python code inside them. This is powerful and dangerous. I've seen developers put database queries inside f-string expressions, creating implicit coupling between presentation and data access layers. It works until you need to test either piece independently. Another gotcha involves dictionary unpacking inside f-strings. Prior to Python 3.11, you could not directly unpack a dictionary into an f-string expression without assigning it to a variable first. The syntax f"{data}" raises a SyntaxError in older versions. If you support multiple Python versions, this trips people up silently. The workaround is straightforward but easy to overlook during a refactor.

Exception handling that actually works

Bare except clauses are the single most common anti-pattern I encounter in code reviews. except: catches everything including KeyboardInterrupt and SystemExit. You should always specify the exception type. At minimum use except Exception: to avoid swallowing system interruptions. The else clause on try statements is underused and misunderstood. The else block runs only if no exception occurred. This is useful for separating the code you are protecting from the code that handles failures. Without it, exceptions raised inside your success path get caught by the except block, which is rarely what you want. I encountered a bug once where a network request wrapper was catching errors from an unrelated logging call that happened to sit in the same try block. The logging library raises its own exceptions when the disk fills up. Those exceptions were being caught and logged as network failures, creating false alerts in the monitoring dashboard. Separating the try block to only cover the network call fixed it immediately.

10 Common Mistakes Beginners Make in Python (And How to Avoid Them | by Yallalarajareddy | Medium
10 Common Mistakes Beginners Make in Python (And How to Avoid Them | by Yallalarajareddy | Medium

Custom exception hierarchies are another area where people either over-engineer or skip entirely. A flat list of custom exceptions is fine for small projects. For larger systems, organizing them under a base exception class like class MyAppError(Exception): makes targeted catching possible. You can catch all app errors at a boundary layer while letting library exceptions propagate unchanged.

Async/await pitfalls that cost real money

Async Python is powerful but unforgiving. The most common mistake is mixing blocking and non-blocking code. Calling a synchronous file operation or HTTP request inside an async function blocks the entire event loop. Every other coroutine waiting for I/O stalls until that call returns. In a web server context, this means one slow synchronous operation can make your entire application appear frozen. The second mistake is forgetting that async functions return coroutines, not results. If you call an async function without awaiting it, you get a coroutine object. If you then pass that coroutine to a function that expects a value, you will get type errors or worse, silent incorrect behavior depending on what the downstream function does with the object. A realistic scenario I dealt with involved a task scheduler that spawned async workers using asyncio.create_task() but never awaited the results. The tasks ran, completed, and their return values were discarded because nothing stored them. The program appeared to work because there were no errors, but the output was silently wrong. Adding a result collection pattern with asyncio.gather() resolved the issue.

Object lifecycle and garbage collection misconceptions

Python's garbage collector uses reference counting as its primary mechanism. When the last reference to an object drops to zero, the object is freed immediately. This is deterministic, unlike many other languages. However, reference cycles exist. Two objects referencing each other will never reach zero references through normal counting alone. The cyclic garbage collector handles this second pass, but it runs periodically and its behavior can be configured. Setting gc.set_debug(gc.DEBUG_STATS) reveals when collection runs and how many objects it finds. I found this useful when diagnosing a memory leak in a long-running service. The leak wasn't from growing references but from objects trapped in cycles that the cyclic collector was too slow to reclaim under high allocation rates. Weak references are the solution when you need to hold a reference without preventing garbage collection. They are commonly needed for caches, observer patterns, and parent-child relationships where the child shouldn't keep the parent alive. The weakref module provides WeakKeyDictionary and WeakValueDictionary, which automatically remove entries when the referenced object is collected.

PPT - Top Common Python Programming Mistakes and How to Fix Them PowerPoint Presentation - ID ...
PPT - Top Common Python Programming Mistakes and How to Fix Them PowerPoint Presentation - ID ...

One practical limitation: weak references do not work with all object types. Built-in types like int and str are immutable and frequently interned, so weak references to them behave unpredictably. Custom classes with __slots__ also have restrictions. If you need weak references to cached values, you typically wrap them in a class or use a different caching strategy entirely.

Thread safety assumptions that break under load

Python's Global Interpreter Lock means only one thread executes Python bytecode at a time. This prevents many race conditions at the language level but creates a false sense of security. Operations that appear atomic in other languages are not necessarily atomic in Python. The isinstance check, attribute access, and the seemingly simple increment operation (x += 1) all involve multiple bytecode instructions. The real problem emerges with compound operations. Reading a value, computing a new value, and writing it back is not atomic even though the individual steps are protected by the GIL. Two threads can interleave between the read and the write. I saw this cause a counter to reach 95 instead of 100 after ten threads each attempted to increment it ten times. The discrepancy was exactly what the interleaving model predicts. The queue module provides thread-safe data structures for passing data between threads. Use queue.Queue instead of building your own lock-protected lists. The overhead is negligible compared to the correctness guarantees, and the blocking get and put methods eliminate entire classes of race conditions by design.

Module import behavior and circular dependencies

Python imports are cached. Once a module is loaded, subsequent imports return the cached module object. This is why circular imports sometimes work and sometimes fail depending on execution order. If module A imports module B, and module B imports module A, the import succeeds only if module A has already executed enough code to define the names that module B needs. A common failure pattern occurs when module B tries to access a function from module A before that function has been defined. The imported module object exists but is incomplete. You get an AttributeError rather than a clear import error, which makes debugging harder than it should be. The workaround is either restructuring to eliminate the circular dependency or deferring the import inside the function that needs it. Deferred imports add a small runtime cost but keep the code decoupled. For most applications the cost is imperceptible. The structural benefit is significant.

Unlocking Data Insights: A Comprehensive Guide to Using Python for Data Manipulation and Analysis
Unlocking Data Insights: A Comprehensive Guide to Using Python for Data Manipulation and Analysis

Understanding Python packaging beyond pip install

Most developers interact with packages through pip, but the packaging ecosystem has deeper mechanics that cause problems when things go wrong. Dependency resolution failures often stem from conflicting version constraints across packages, not from broken packages themselves. Pip's resolver reports these as solver errors without always explaining which specific constraint triggered the conflict. Venv and venv modules create isolated environments but do not manage dependencies. That responsibility falls to pip inside the environment. The common mistake is installing packages globally when you meant to install them locally, or vice versa. Checking which Python and which pip are active before running install commands takes five seconds and prevents hours of confusion later. Virtual environments based on the same Python installation share the same site-packages directory. Creating multiple venvs from the same interpreter does not give you separate dependency sets unless you also use pip-tools or poetry to pin versions. Two projects with different requirements for the same package will conflict if installed into shared locations. This is why environment isolation matters beyond just keeping files separate.

Decorators that mutate state unexpectedly

Decorators run at import time, not at call time. This means any code inside a decorator executes when the module loads. Side effects in decorators are a frequent source of bugs, especially in web frameworks where imports happen during application startup. A decorator that registers routes, connects to databases, or modifies global state will do so before any request arrives. Function attributes set inside decorators persist across calls. This is sometimes intentional, like caching metadata, but can cause confusion when the same decorated function is used in multiple contexts with different expectations. The wrapped function retains its original name and docstring only if the decorator uses functools.wraps, which most properly written decorators do. Skipping wraps preserves the identity but loses documentation, which matters for frameworks that inspect function metadata at runtime. Class decorators receive the class object after definition and can modify it in place. This is powerful but easy to misuse. A decorator that adds methods to a class affects all instances and all subclasses, which is sometimes desired but rarely obvious from the decorator signature alone. Documenting decorator behavior in docstrings is a small effort that prevents significant confusion later.

Performance profiling before optimizing

The instinct to optimize code before measuring is almost always wrong. Python provides cProfile for function-level profiling and line_profiler for line-by-line analysis. cProfile adds overhead but gives you a reliable call graph with timing data. Running it on your actual workload rather than a synthetic benchmark is the difference between finding the real bottleneck and chasing a red herring. I once spent three days optimizing a sorting routine that was supposedly slow. The profiler showed the sorting took less than two percent of total runtime. The actual bottleneck was a database query inside a loop that should have been a single join operation. Optimizing the sort changed total execution time by less than one percent. Profiling took twenty minutes and saved three days of wasted effort. Memory profiling with tracemalloc or objgraph helps when CPU time is not the constraint. Object creation patterns in tight loops can generate enormous garbage collection pressure even when total memory usage stays low. Tracking allocations over time reveals these patterns more clearly than peak memory metrics.

Top 10 Common Python Errors And How To Fix Them
Top 10 Common Python Errors And How To Fix Them

There is no comprehensive Field Guide For Python Common Mistakes To Avoid because the mistakes keep evolving as the language changes. New versions introduce features like match statements and positional-only parameters, each with their own gotchas. The most reliable approach is reading release notes, testing edge cases in isolation, and maintaining a personal reference of patterns that have caused problems in your own code. The patterns in this guide represent the ones I encounter most frequently across different projects and teams.