AI Slop in Python: 10 Patterns Coding Agents Leave Behind
The Python patterns coding agents leave most often, the cleaner version of each, and the aislop rule that flags it.
Ten patterns coding agents often leave in Python, each with a cleaner version and the aislop rule that flags it. They run and usually pass tests, so they get through review. For the language-neutral list, see 20 AI slop code examples.
Hidden errors and shared state
1. The broad except that returns a default
def load_settings(path):
try:
with open(path) as f:
return json.load(f)
except Exception:
return {}A missing file, a permissions error and malformed JSON all become “no settings”. Catch the exception you expect and let the rest raise:
def load_settings(path):
try:
with open(path) as f:
return json.load(f)
except FileNotFoundError:
return DEFAULT_SETTINGSRules: ai-slop/python-broad-except, and
ai-slop/python-bare-except for the even broader except:.
See swallowed exception.
2. The mutable default argument
def add_tag(tag, tags=[]):
tags.append(tag)
return tagsThe list is created once and shared by every call that omits tags,
so state leaks between calls. Default to None instead:
def add_tag(tag, tags=None):
tags = [] if tags is None else tags
tags.append(tag)
return tagsRule: ai-slop/python-mutable-default.
Verbose shapes from SlopCodeBench
The SlopCodeBench paper found agent-written Python more verbose than human-maintained code. These four rules target the most repeatable causes.
3. range(len()) loops
# slop
for i in range(len(users)):
names.append(users[i].name)
# clean
names = [user.name for user in users]Iterate directly, or use enumerate when you need the index.
Rule: ai-slop/python-range-len-loop.
4. Chained dict.get with empty defaults
# slop
city = payload.get("user", {}).get("address", {}).get("city", "")
# clean: validate once at the boundary
user = User.model_validate(payload)
city = user.address.cityEach {} fallback hides missing data. Validate once at the boundary
with a typed model (Pydantic, a dataclass, a TypedDict) so real gaps fail loudly.
Rule: ai-slop/python-chained-dict-get.
5. The if/elif dispatch ladder
# slop
if event == "created":
handle_created(data)
elif event == "updated":
handle_updated(data)
elif event == "deleted":
handle_deleted(data)
elif event == "archived":
handle_archived(data)
# clean
HANDLERS = {
"created": handle_created,
"updated": handle_updated,
"deleted": handle_deleted,
"archived": handle_archived,
}
HANDLERS[event](data)The chain grows by one branch per requirement. A dispatch table, or match
on Python 3.10+, makes the next case one line.
Rule: ai-slop/python-repetitive-dispatch, which flags four or more
branches on the same value.
6. The isinstance ladder
if isinstance(value, int):
return str(value)
elif isinstance(value, float):
return f"{value:.2f}"
elif isinstance(value, Decimal):
return format(value, "f")
elif isinstance(value, str):
return valueThe same chain, on types. Normalize the input once, or use a handler map or
functools.singledispatch. Rule: ai-slop/python-isinstance-ladder.
Leftovers and undeclared dependencies
7. Debug print() calls
print("DEBUG response:", response.json())A leftover from the agent checking its work, and a leak once the response holds a token. Use
logging or delete it.
Rule: ai-slop/python-print-debug.
8. Imports the project does not declare
import requests # not in pyproject.toml
from dateutil import parser # installed globally on one laptopIt works on the agent’s machine and fails in CI. A package that does not exist at all invites
slopsquatting.
aislop checks imports against the standard library, pyproject.toml,
requirements files and uv workspaces.
Rule: ai-slop/hallucinated-import.
9. Docstrings and comments that narrate
def get_user(user_id):
# Get the user from the database using the user id
# and return the user object
return db.users.get(user_id)The comment repeats the code. Keep comments for why. See narrative comment.
10. Unused imports and dead helpers
import os, re, sys # only os is used
def parse_config_v2(path):
return parse_config(path)Imports from code that was rewritten, and a wrapper that only adds a name. Delete both. See unused import and thin wrapper.
Scan a Python project
uvx aislop scan # or: pipx install aislop && aislop scan
aislop scan --changes # only what changed
aislop fix # safe, mechanical fixesThe PyPI package launches the same CLI as npm, so it needs Node.js on PATH
(other runners).
Besides these rules, aislop runs ruff, a complexity check, and pip-audit
plus security pattern checks, and detects Django, Flask and FastAPI. Missing tools are skipped;
aislop doctor shows what is installed. See
supported languages.
To catch these patterns as the agent writes them, give it rules and a hook: Claude Code, Cursor, Codex. For TypeScript, see AI slop in React and TypeScript.
Frequently asked questions
What does AI slop look like in Python?
Broad except blocks that return a default, mutable default arguments, range(len()) loops, chained .get(..., {}) lookups, long if/elif and isinstance chains, leftover print() calls, undeclared imports and comments that narrate the code.
Doesn't ruff already catch this?
Ruff catches a lot, and aislop runs it for Python formatting and lint. The AI slop rules cover valid, lint-clean code that ruff leaves alone, such as silent fallbacks, dispatch ladders, chained defaults and imports missing from the manifest.
How do I scan Python code for AI slop?
Run uvx aislop scan, or pipx install aislop, from the project root. The PyPI package launches the same CLI as npm, so it needs Node.js on PATH.
Where did aislop's Python rules come from?
From real repositories and from research. The range(len()), chained dict.get, repetitive dispatch and isinstance ladder rules come from the verbosity findings in the SlopCodeBench paper (arXiv 2603.24755), which studied agents writing Python.
Is all agent-written Python slop?
No. These patterns are worth flagging because they pass tests and look reasonable, so they get through review unless something names them.