Python
Writes, debugs, and reviews Python code — runtime traps, packaging, typing, async, tests, performance. Use when Python raises or misbehaves: ModuleNotFoundError, circular imports, …
Iván
@ivangdavila
What This Skill Does
Debugs and reviews Python code across runtime errors, packaging conflicts, concurrency issues, typing, testing, and performance problems. Covers common traps like ModuleNotFoundError, circular imports, mutable defaults, GIL deadlocks, and silent wrong answers.
Replaces manually tracing Python bugs through stack overflow and trial-and-error by providing structured debugging chains for every common failure mode.
When to Use It
- Debug a traceback whose cause is not obvious within ten seconds
- Resolve dependency conflicts between pip, uv, poetry, or lockfiles
- Fix a pytest suite that passes but should not, or mocks patching the wrong module
- Clear mypy or pyright type errors in a codebase
- Diagnose a slow script that eats RAM or gets OOM-killed
- Investigate a hang or deadlock in threads, asyncio, or multiprocessing code
Install
$ openclaw skills install @ivangdavila/pyUser preferences live in ~/Clawic/data/py/config.yaml (see Configuration); nothing else is stored on the user's machine. If you have data at an old location (~/py/ or ~/clawic/py/), move it to ~/Clawic/data/py/.
When To Use
- Writing or reviewing Python — scan Core Rules and Output Gates before committing
- Debugging wrong results without exceptions: state leaking across calls, aliased mutations, silent coercion
- Any traceback whose cause is not obvious in ten seconds, plus hangs, memory growth, and segfaults
- Environment and dependency work: venvs, lockfiles, editable installs, publishing, version upgrades
- Choosing a concurrency model, a data model, or a typing strategy, and living with the consequences
- Hardening a script into a tool: arguments, exit codes, logging, timeouts, retries, untrusted input
- Not for library-specific issues —
pandas,numpy,django,fastapi,flaskhave their own skills
Quick Reference
| Situation | Play |
|---|---|
| Wrong value, no exception | repr() at input, midpoint, output; the first wrong one is the bug — suspect aliasing, coercion, stale default → debugging.md |
| A traceback you have not seen before | Message → cause table, then the matching chain → debugging.md |
Installed it, still ModuleNotFoundError | Wrong interpreter: check sys.executable, then use python -m pip → packaging.md |
| venvs, lockfiles, uv vs pip vs poetry, publishing | Applications pin everything with hashes; libraries declare ranges → packaging.md |
cannot import name X from partially initialized module | Circular import; switch to import a + a.f() before restructuring → imports.md |
is vs ==, float rounding, money, NaN, string surgery | is only for None/True/False/sentinels; Decimal from strings → types.md |
| Aliasing, copies, ordering, membership cost | [[]]*3 shares one list; x in list is O(n) → collections.md |
| State leaks across calls: defaults, closures, decorators, generators | Defaults evaluate once, at def time → functions.md |
Class design: shared attributes, hashability, MRO, __slots__ | __eq__ without __hash__ makes instances unhashable → classes.md |
| dict vs dataclass vs NamedTuple vs pydantic; validating input | Validate once at the boundary, plain objects inside → data-modeling.md |
| Slow, hanging, or racy: GIL, threads, asyncio, multiprocessing | Pure-Python CPU → processes; I/O → threads or asyncio → concurrency.md |
| Too slow, or too much memory | Profile first, then fix the top line or nothing → performance.md |
| Tests pass but should not; mock patches nothing; async tests skipped | Patch where the name is USED; autospec=True → testing.md |
| mypy or pyright errors, annotating a legacy codebase | Freeze a baseline, then ratchet one package at a time → type-checking.md |
| Formatting, import order, lint rules, pre-commit, style review comments | One formatter owns layout; ruff check for bugs; same versions in hook and CI → linting.md |
UnicodeDecodeError, CSV and JSON traps, atomic writes, temp files | Declare encoding="utf-8"; write temp then os.replace → files.md |
| Timezones, DST, parsing dates, measuring elapsed time | Aware datetimes everywhere; time.monotonic() for durations → datetime.md |
| Calling git/ffmpeg/anything external, hanging or failing silently | run([...], check=True, timeout=…), never shell=True with interpolation → subprocess.md |
| Writing a script or CLI: arguments, exit codes, pipes, signals | sys.exit(main()); stdout for data, stderr for logs → cli.md |
| Nothing appears in the logs, or deciding what to log | Four gates: basicConfig no-op, logger level, handler level, propagation → logging.md |
| Exception design, chaining, retries, timeouts | raise X from exc; exponential backoff with jitter → errors.md |
| Calling an HTTP API: sessions, status codes, redirects, streaming a big body | One client per process; raise_for_status() before .json() → http.md |
A local database, cache, or queue in a file; database is locked | WAL plus an explicit busy timeout; one connection per thread → sqlite.md |
| Untrusted input: pickle, eval, SQL, paths, archives, secrets | Unpickling executes code; parameterize SQL; contain paths → security.md |
| "Is there something in the stdlib for this?" | Counter, deque, bisect, itertools, pathlib, secrets, sqlite3 → stdlib.md |
| Notebook works for you and nobody else; results change between runs | Restart and run all; %pip not !pip; strip outputs before committing → notebooks.md |
| Upgrading Python, choosing a version, a module that vanished | Run the suite with -W error::DeprecationWarning on the OLD version first → versions.md |
| Anything else | Core Rules below, then reproduce with python -I -c '<the five suspect lines>' and re-add one thing at a time |
Each file above is one sub-job and is self-contained: read SKILL.md by default, open exactly one guide when the situation matches.
Core Rules
- No mutable defaults:
def f(xs=None)thenif xs is None: xs = []. Neverxs = xs or []— a caller passing an empty list to be filled gets a fresh list instead; their reference stays empty. isonly forNone,True,False, and sentinel objects;==for everything else. Interning makesison ints and strings pass in tests and fail in production (types.md).- Never mutate the collection you iterate — dicts raise
RuntimeError, lists silently skip elements. Iterate a copy (for x in list(xs)) or collect changes and apply after. - Match concurrency to workload: pure-Python CPU →
multiprocessing; I/O → threads or asyncio; threads never speed up pure-Python CPU work, because the GIL serializes bytecode (concurrency.md). except Exception:, never bareexcept:— bare also catchesKeyboardInterruptandSystemExit, making the process unkillable. Re-raise with bareraise, keeping the original traceback (errors.md).- Files, locks, sockets, connections: always
with. CPython's refcounting closes leaked handles by accident; exceptions and other interpreters (PyPy) expose the leak. - Money and exact decimals:
decimal.Decimal('1.10')from strings —Decimal(1.1)imports the float error it was meant to avoid. Float comparisons viamath.isclose(types.md). - Declare
encoding='utf-8'at every I/O boundary — the default follows the platform locale until UTF-8 becomes the default (PEP 686,python >=3.15), so the code breaks first on Windows and in lean containers (files.md). - Guard entry points with
if __name__ == "__main__":— top-level code runs on every import and again in everymultiprocessingspawn child (imports.md).
Version Floors
The syntax and stdlib floors that shape everyday code. Individual guides carry additional floors — mostly changed defaults and new keyword arguments — inline in this same python >=X.Y form, next to the instruction each one gates. Support windows, removals, and the upgrade procedure: versions.md.
| Feature | Needs |
|---|---|
Guaranteed dict insertion order, dataclasses, breakpoint(), from __future__ import annotations, sys.stdout.reconfigure | python >=3.7 |
Walrus :=, positional-only /, functools.cached_property, TypedDict/Protocol/Literal/Final, basicConfig(force=), self-documenting f"{value=}" | python >=3.8 |
Builtin generics list[int], d1 | d2 (dict merge), zoneinfo, removeprefix/removesuffix, functools.cache, asyncio.to_thread, importlib.resources.files, Path.is_relative_to, graphlib, argparse.BooleanOptionalAction, executor.shutdown(cancel_futures=) | python >=3.9 |
X | Y unions, match, zip(strict=True), dataclass slots=/kw_only=, itertools.pairwise, ParamSpec, EncodingWarning and -X warn_default_encoding | python >=3.10 |
ExceptionGroup/except*, asyncio.TaskGroup, tomllib, Self, StrEnum, exc.add_note(), contextlib.chdir, full-ISO fromisoformat, NotRequired | python >=3.11 |
PEP 695 generics (def f[T]()), @override, itertools.batched, tarfile(filter="data") | python >=3.12 |
Experimental free-threaded build; PEP 594 module removals (cgi, telnetlib, …) | python >=3.13 |
forkserver as the Linux multiprocessing default, lazy annotations (PEP 649) | python >=3.14 |
| UTF-8 as the default text encoding everywhere (PEP 686), retiring the locale default | python >=3.15 |
Output Gates
Before delivering Python code, check:
- No mutable default argument, and no
xs or []where an empty argument is meaningful - Every text
open,subprocess, and decode boundary declaresencoding="utf-8"; every file, lock, socket, and connection is acquired in awith - Every network, subprocess, lock, and queue call has a timeout
- Exceptions: narrowest class caught, chained with
from, nothing swallowed without a log line or a written reason - Logging through a module logger with lazy
%sarguments and no secrets - Syntax and stdlib stay within
min_python; annotations matchtype_strictness - Nothing user-supplied reaches
eval,pickle, a shell string, an SQL string, or a path join without containment - The new test was seen failing without the fix in place —
change_workflowdecides when that run happens, never whether it happened
Configuration
User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/py/config.yaml. Never interview the user — record a preference the moment it is stated.
| Variable | Type | Default | Effect |
|---|---|---|---|
| min_python | 3.9 | 3.10 | 3.11 | 3.12 | 3.13 | 3.14 | 3.11 | Gates which Version Floors features may be emitted unguarded, and which fallback appears instead |
| package_manager | pip | uv | poetry | pdm | conda | pip | Chooses the install, lock, and venv commands in every example (packaging.md) |
| type_strictness | none | gradual | strict | gradual | How far annotations go: none skips hints, gradual annotates public boundaries, strict turns on disallow_untyped_defs (type-checking.md) |
| test_runner | pytest | unittest | pytest | Shape of emitted tests, fixtures, and mocking guidance (testing.md) |
| target_os | linux | macos | windows | cross | cross | Path, encoding, and multiprocessing start-method assumptions; cross flags all three |
| src_layout | src | flat | src | Project scaffolding, editable-install and import advice (packaging.md) |
| line_length | number (79-120) | 88 | Formatting of emitted code and the ruff/black config value (linting.md) |
| change_workflow | test-first | fix-first | test-first | Order of the work: test-first writes the failing test before the fix, fix-first patches first and backfills the test against the reverted change (testing.md) |
Preference areas — customizable dimensions; a stated preference gets recorded in config.yaml and applied:
- Tooling: formatter and linter (ruff vs black+flake8), checker (mypy vs pyright), task runner, notebook vs module workflow — affects every emitted config block (
linting.md) - Conventions: docstring style (Google/NumPy/reST), naming, module granularity, error-message phrasing — affects generated code and review comments
- Platform: deployment target (container, serverless, desktop, embedded), CPU architecture, private package index, CI provider — affects
packaging.mdandversions.mdguidance - Safety posture: how aggressively to flag
pickle,eval,shell=True, missing timeouts and unpinned dependencies — affectssecurity.mdandcli.md - Dependencies: stdlib-only constraints, banned or mandated libraries (requests vs httpx, pydantic vs dataclasses), tolerance for new transitive dependencies — affects every recommendation
- Output format: whole file vs minimal diff, how much explanation, whether tests accompany every change — affects the shape of the answer, never the correctness rules
- Work order: which gates run before a change is proposed rather than after — checker and suite green first, a plan approved before editing, a
--dry-runpass on destructive scripts, a profile before any optimization — affects the sequence of every task, never the correctness rules
Traps
| Trap | Why it fails | Do instead |
|---|---|---|
Bare pip install | Installs into whatever interpreter is first on PATH — the "installed it, still ModuleNotFoundError" loop | python -m pip install inside an activated venv (packaging.md) |
except Exception: pass | Turns a crash into a wrong answer three layers away | Log with logger.exception, or write down why this failure is expected (errors.md) |
assert user.is_admin as a check | python -O removes every assert, including that one | Raise explicitly (security.md) |
time.time() to measure a duration | NTP can step the clock backwards mid-measurement and produce negative elapsed time | time.monotonic() (datetime.md) |
datetime.utcnow() | Returns a NAIVE datetime holding UTC: compares wrong against aware values, deprecated in python >=3.12 | datetime.now(timezone.utc) (datetime.md) |
| f-strings inside logging calls | Formats even when the level is disabled, and every line becomes a unique string to the aggregator | log.info("user %s", uid) (logging.md) |
sys.path.append(...) to fix an import | Works from one entry point, breaks under pytest, packaging, and any other cwd | Editable install with a src/ layout (imports.md) |
shell=True with an interpolated value | Command injection — and /bin/sh is not bash | Argument list; shlex.quote if a shell is unavoidable (subprocess.md) |
verify=False to silence an SSL error | Disables peer authentication for every request in the process | Install the CA bundle (security.md) |
pickle across a process, user, or network boundary | Unpickling executes attacker-controlled code before you can inspect it | JSON/msgpack, or an HMAC-signed payload (security.md) |
raise e when re-raising | Appends the current line and hides where the exception really came from | Bare raise (errors.md) |
| Type hints treated as validation | Never enforced at runtime: def f(x: int) happily takes a string | Checker in CI plus runtime validation at the boundary (type-checking.md) |
x in list or list.pop(0) inside a loop | O(n) per operation makes the loop O(n²) | set for membership, deque for both ends (collections.md) |
Where Experts Disagree
- asyncio vs threads. asyncio is not "faster Python": for moderate I/O concurrency,
ThreadPoolExecutormatches it with far less ceremony. Boundary: high connection counts AND an async-native stack end to end → asyncio; one blocking driver anywhere in the chain forfeits the benefit (concurrency.md). - How much typing. The strict school annotates everything and treats
Anyas a defect; the pragmatic school types boundaries and lets internals be inferred. Boundary: libraries and cross-team APIs → annotate fully; single-owner scripts and glue → boundaries only. Either way, one checker in CI (type-checking.md). - Packaging tooling.
venv+pipexists on every machine and surprises nobody;uv/poetry/hatchare faster and manage more. Boundary: a project with a real dependency problem, many contributors, or several interpreters justifies the tool; one that installs three libraries does not (packaging.md). - EAFP vs LBYL. Python's culture prefers
try/exceptover pre-checking, and it is genuinely more correct under concurrency — the file can disappear betweenexists()andopen(). Boundary: LBYL when the operation is expensive or irreversible, EAFP when the failure is cheap and the race is real.
Related Skills
More Clawic skills, get them at https://clawic.com/skills/py (install if the user confirms):
pandas— DataFrame-specific traps and idiomsfastapi— async web APIs in Pythondjango— Django ORM and framework patternsprofiling— when the question is "why is it slow", measure firstdebugging— language-agnostic fault isolation (bisection, hypothesis discipline, minimal repro); the Python-specific version of that job is this skill'sdebugging.md
Feedback
- If useful, star it: https://clawic.com/skills/py
- Latest version: https://clawic.com/skills/py
Part of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/py.
Top skills in this category
Skill Vetter
@spclaudehomeSecurity-first skill vetting for AI agents. Use before installing any skill from ClawdHub, GitHub, or other sources. Checks for red flags, permission scope, and suspicious patterns.
Github
@steipeteInteract with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Humanizer
@biostartechnologyRemove signs of AI-generated writing from text. Use when editing or reviewing text to make it sound more natural and human-written. Based on Wikipedia's comprehensive "Signs of AI writing" guide. Detects and fixes patterns including: inflated symbolism, promotional language, superficial -ing analyses, vague attributions, em dash overuse, rule of three, AI vocabulary words, negative parallelisms, and excessive conjunctive phrases.
Free Ride - Unlimited free AI
@shaivpidadiManages free AI models from OpenRouter for OpenClaw. Automatically ranks models by quality, configures fallbacks for rate-limit handling, and updates opencla...
Elite Longterm Memory
@nextfrontierbuildsUltimate AI agent memory system for Cursor, Claude, ChatGPT & Copilot. WAL protocol + vector search + git-notes + cloud backup. Never lose context again. Vibe-coding ready.