plugins/python-master/skills/python-gotchas/SKILL.md
Complete Python gotchas reference. PROACTIVELY activate for: (1) Mutable default arguments, (2) Mutating lists while iterating, (3) is vs == comparison, (4) Late binding in closures, (5) Variable scope (LEGB), (6) Floating point precision, (7) Exception handling pitfalls, (8) Dict mutation during iteration, (9) Circular imports, (10) Class vs instance attributes. Provides: Problem explanations, code examples, fixes for each gotcha. Ensures bug-free Python code.
npx skillsauth add JosiahSiegel/claude-plugin-marketplace python-gotchasInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Gotcha | Problem | Fix |
|--------|---------|-----|
| Mutable default | def f(x=[]) | Use None, create in function |
| Iterate + mutate | Skips items | Iterate over copy items[:] |
| is vs == | Identity vs value | Use is only for None |
| Late binding | lambda: i captures var | lambda i=i: i |
| Float precision | 0.1 + 0.2 != 0.3 | math.isclose() |
| Dict mutation | RuntimeError | list(d.keys()) |
| Class attribute | Shared mutable | Init in __init__ |
| Falsy Values | Examples |
|--------------|----------|
| Boolean | False |
| None | None |
| Numbers | 0, 0.0, 0j |
| Empty collections | "", [], {}, set() |
| Scope Rule | Order |
|------------|-------|
| LEGB | Local → Enclosing → Global → Built-in |
| global | Access module-level variable |
| nonlocal | Access enclosing function variable |
Use for debugging and prevention:
Related skills:
python-fundamentals-313python-testingpython-type-hintsPython has several well-known pitfalls that trip up developers of all experience levels. Understanding these gotchas prevents subtle bugs and unexpected behavior.
# BAD: Mutable default argument
def add_item(item, items=[]):
items.append(item)
return items
# Unexpected behavior!
print(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b'] - NOT ['b']!
print(add_item("c")) # ['a', 'b', 'c']
Default arguments are evaluated once when the function is defined, not each time it's called. The same list object is reused across all calls.
# GOOD: Use None as default
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
# Works correctly
print(add_item("a")) # ['a']
print(add_item("b")) # ['b']
print(add_item("c")) # ['c']
# BAD: All mutable types have this issue
def bad_dict(data={}): ...
def bad_set(data=set()): ...
def bad_class(config=SomeClass()): ...
# GOOD: Always use None
def good_dict(data=None):
if data is None:
data = {}
return data
def good_set(data=None):
if data is None:
data = set()
return data
# BAD: Modifying list during iteration
numbers = [1, 2, 3, 4, 5, 6]
for num in numbers:
if num % 2 == 0:
numbers.remove(num)
print(numbers) # [1, 3, 5] - missed 4!
The iterator uses indices internally. When you remove an item, all subsequent indices shift, causing items to be skipped.
# GOOD: Iterate over a copy
numbers = [1, 2, 3, 4, 5, 6]
for num in numbers[:]: # Slice creates a copy
if num % 2 == 0:
numbers.remove(num)
print(numbers) # [1, 3, 5]
# GOOD: Use list comprehension (preferred)
numbers = [1, 2, 3, 4, 5, 6]
numbers = [num for num in numbers if num % 2 != 0]
print(numbers) # [1, 3, 5]
# GOOD: Use filter
numbers = [1, 2, 3, 4, 5, 6]
numbers = list(filter(lambda x: x % 2 != 0, numbers))
print(numbers) # [1, 3, 5]
# GOOD: Iterate backwards (for in-place modification)
numbers = [1, 2, 3, 4, 5, 6]
for i in range(len(numbers) - 1, -1, -1):
if numbers[i] % 2 == 0:
del numbers[i]
print(numbers) # [1, 3, 5]
is vs ==# Comparing values vs identity
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True - same values
print(a is b) # False - different objects
# Integer interning gotcha
x = 256
y = 256
print(x is y) # True (integers -5 to 256 are interned)
x = 257
y = 257
print(x is y) # False! (outside interning range)
== to compare valuesis only for identity (singletons like None, True, False)# GOOD: Correct usage
if value is None:
...
if value == other_value:
...
# BAD: Don't use `is` for value comparison
if value is 0: # Wrong!
...
# Closure gotcha
functions = []
for i in range(3):
functions.append(lambda: i)
# All return the same value!
print([f() for f in functions]) # [2, 2, 2]
The lambda captures the variable i, not its value. By the time lambdas are called, i is 2.
# GOOD: Capture value with default argument
functions = []
for i in range(3):
functions.append(lambda i=i: i) # Default arg captures value
print([f() for f in functions]) # [0, 1, 2]
# GOOD: Use functools.partial
from functools import partial
def return_value(x):
return x
functions = [partial(return_value, i) for i in range(3)]
print([f() for f in functions]) # [0, 1, 2]
# BAD: This raises UnboundLocalError
x = 10
def increment():
x = x + 1 # Error! x is local but used before assignment
return x
# GOOD: Use global (sparingly)
def increment():
global x
x = x + 1
return x
# BETTER: Avoid global, pass as parameter
def increment(x):
return x + 1
# Missing comma creates concatenation
items = [
"apple"
"banana" # Oops! Missing comma
"cherry"
]
print(items) # ['applebanana', 'cherry']
# CORRECT
items = [
"apple",
"banana",
"cherry",
]
# BAD: Can't concatenate str and int
name = "User"
count = 42
# message = "Hello " + name + ", you have " + count + " messages" # TypeError!
# GOOD: Use f-strings
message = f"Hello {name}, you have {count} messages"
# GOOD: Use str()
message = "Hello " + name + ", you have " + str(count) + " messages"
# Class method gotcha
class MyClass:
def __init__(self, callbacks=[]): # BAD: Mutable default!
self.callbacks = callbacks
def add_callback(self, func):
self.callbacks.append(func)
obj1 = MyClass()
obj2 = MyClass()
obj1.add_callback(lambda: print("Hello"))
# obj2 also has the callback!
print(len(obj2.callbacks)) # 1
class MyClass:
def __init__(self, callbacks=None):
self.callbacks = callbacks if callbacks is not None else []
def add_callback(self, func):
self.callbacks.append(func)
# These are all falsy
falsy_values = [
False,
None,
0,
0.0,
0j,
"",
[],
{},
set(),
range(0),
]
# Gotcha: Empty collections are falsy
data = []
if data:
print("Has data") # Not printed
else:
print("No data") # Printed
# But None and empty are different!
if data is None:
print("Is None") # Not printed
elif data == []:
print("Is empty list") # Printed
# BAD: Ambiguous check
def process(items):
if not items: # Could be None OR empty
return
# GOOD: Be explicit about what you're checking
def process(items):
if items is None:
raise ValueError("items cannot be None")
if len(items) == 0:
return # Early return for empty list
# Floating point arithmetic isn't exact
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False!
import math
from decimal import Decimal
# GOOD: Use math.isclose for comparisons
print(math.isclose(0.1 + 0.2, 0.3)) # True
# GOOD: Use Decimal for financial calculations
price = Decimal("19.99")
tax = Decimal("0.0875")
total = price * (1 + tax)
print(total) # 21.739125
# Round appropriately
print(round(total, 2)) # 21.74
# BAD: Catches everything, including KeyboardInterrupt
try:
risky_operation()
except:
pass
# BAD: Too broad
try:
risky_operation()
except Exception:
pass # Silently ignores all errors
# GOOD: Catch specific exceptions
try:
risky_operation()
except ValueError as e:
logger.error(f"Invalid value: {e}")
except ConnectionError as e:
logger.error(f"Connection failed: {e}")
raise # Re-raise after logging
# Python 3: Exception variable is deleted after except block
try:
1 / 0
except ZeroDivisionError as e:
error = e # Save if needed
print(e)
# print(e) # NameError! e is deleted
print(error) # OK
# Since Python 3.7, dicts maintain insertion order
d = {"b": 2, "a": 1, "c": 3}
print(list(d.keys())) # ['b', 'a', 'c'] - insertion order
# But comparison ignores order
d1 = {"a": 1, "b": 2}
d2 = {"b": 2, "a": 1}
print(d1 == d2) # True
# BAD: RuntimeError
d = {"a": 1, "b": 2, "c": 3}
for key in d:
if d[key] == 2:
del d[key] # RuntimeError: dictionary changed size during iteration
# GOOD: Iterate over copy of keys
d = {"a": 1, "b": 2, "c": 3}
for key in list(d.keys()):
if d[key] == 2:
del d[key]
print(d) # {'a': 1, 'c': 3}
# GOOD: Dict comprehension
d = {"a": 1, "b": 2, "c": 3}
d = {k: v for k, v in d.items() if v != 2}
# module_a.py
from module_b import func_b # Fails if module_b imports from module_a
def func_a():
return func_b()
# SOLUTION: Import inside function
def func_a():
from module_b import func_b
return func_b()
# OR: Import module, not function
import module_b
def func_a():
return module_b.func_b()
# BAD: File named random.py shadows stdlib
# random.py (your file)
import random # Imports YOUR file, not stdlib!
random.randint(1, 10) # AttributeError
# Python 3.13 now warns about this!
class MyClass:
shared_list = [] # Class attribute - shared by all instances!
def add_item(self, item):
self.shared_list.append(item)
a = MyClass()
b = MyClass()
a.add_item("hello")
print(b.shared_list) # ['hello'] - Oops!
# GOOD: Initialize in __init__
class MyClass:
def __init__(self):
self.items = [] # Instance attribute - unique to each instance
def add_item(self, item):
self.items.append(item)
| Gotcha | Problem | Solution |
|--------|---------|----------|
| Mutable defaults | def f(x=[]) | Use None, create in function |
| Mutating while iterating | Skips items | Iterate over copy or use comprehension |
| is vs == | Identity vs equality | Use is only for None, True, False |
| Late binding | Captures variable, not value | Use default argument to capture |
| Falsy values | Empty != None | Be explicit in checks |
| Float precision | 0.1 + 0.2 != 0.3 | Use math.isclose() or Decimal |
| Bare except | Catches too much | Catch specific exceptions |
| Dict iteration | Can't modify during | Iterate over list(d.keys()) |
| Circular imports | Import errors | Import inside function or import module |
| Class attributes | Shared unexpectedly | Initialize in __init__ |
development
Use for Clerk sessions, tokens, webhooks, orgs, and security. PROACTIVELY activate for session tokens, JWT templates, getToken(), custom claims, pending sessions, multi-session UX, organizations, roles, permissions, system vs custom permissions, features/plans, MFA/passkeys/password policy/bot protection, Clerk webhooks, Svix signatures, verifyWebhook(), user/org sync, retries/replays, environment variables, custom domains, secret rotation, logs, and auth security reviews. Provides token semantics, webhook idempotency, authorization defaults, and hardening checklist.
tools
Use for Clerk in Next.js. PROACTIVELY activate for @clerk/nextjs setup, App Router auth()/currentUser(), clerkMiddleware(), proxy.ts/middleware.ts, createRouteMatcher(), protected pages/layouts/Route Handlers/Server Actions/API routes/tRPC, auth.protect() role/permission/token checks, ClerkProvider placement, server-only clerkClient, Link prefetch, redirects, 401/404 auth failures, custom domains, __clerk proxy paths, and deployment gotchas. Provides file patterns, server/client boundary rules, matcher templates, and production checks.
development
Use for Clerk frontend auth flows. PROACTIVELY activate for React, JavaScript, Vue, Nuxt, Astro, Expo, React Router, TanStack React Start, or SPA setup; ClerkProvider and publishable-key wiring; SignIn/SignUp/UserButton/UserProfile/OrganizationSwitcher; custom useUser/useAuth/useClerk/useSignIn/useSignUp/useSession/useOrganization flows; multi-session UX; cross-origin getToken() fetches; loading states, redirects, routing, CORS/cookies, or hydration bugs. Provides SDK selection, UI patterns, token-fetch templates, and frontend gotchas.
development
Use for Clerk dev/prod readiness, deployment, and multi-language implementation planning. PROACTIVELY activate for environment variables, pk_test/sk_test vs pk_live/sk_live, local dev, preview/staging/prod instances, domains/DNS, redirects, OAuth credentials, custom domains/proxy, authorizedParties, CSP, CORS/cookies, webhooks/tunnels, Vercel/Netlify/Cloudflare/API gateways, monitoring/troubleshooting, and backends in Node/Express/Fastify, Python/FastAPI/Django/Flask, Go, Ruby/Rails, Java/Spring, .NET, PHP/Laravel. Provides checklists, rollout plans, and language-portable patterns.