opinionated-python-development/skills/python-programmer/SKILL.md
Python-specific idioms, philosophy, and expert-level patterns. Use when working with Python code, including Jupyter notebooks (.ipynb). Covers Pythonic thinking, common pitfalls from other language backgrounds, testing ecosystem navigation, type hints trade-offs, and when to use modern Python features.
npx skillsauth add pyroxin/opinionated-claude-skills python-programmerInstall 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.
<skill_scope skill="python-programmer"> This skill provides guidance on Python-specific idioms, philosophy, and expert-level judgment calls. Python's design emphasizes readability and "one obvious way" to do things, but achieving truly Pythonic code requires understanding when and why to use Python's idioms.
Related skills:
software-engineer — Core engineering philosophy, system design principlesfunctional-programmer — When functional approaches are clearertest-driven-development — Testing philosophy and TDD principles
</skill_scope><when_to_use> Use this skill when:
<core_philosophy> For foundational software engineering principles, see the software-engineer skill.
Python's design philosophy is captured in "The Zen of Python" (import this). Key principles:
Quote to remember: "Explicit is better than implicit. Simple is better than complex. Readability counts." — Tim Peters, PEP 20
In practice:
Staff insight: The Zen is philosophy, not law. Sometimes implicit is fine (e.g., context managers hide __enter__ and __exit__). Sometimes there are two ways (e.g., list comprehension vs map). The Zen guides judgment; it doesn't eliminate it.
<pythonic_vs_readable>
The Zen says both "Explicit is better than implicit" and to use Python idioms. When they conflict, optimize for readers.
| Code Characteristic | Use Pythonic Idiom | Use Explicit Form | |---------------------|-------------------|-------------------| | Reader must pause to parse | No | Yes | | Requires advanced feature knowledge | No | Yes | | In critical path / main logic | No | Yes | | In isolated utility function | Yes | Maybe | | Junior engineer would need to look it up | No | Yes | | Saves 1-2 lines at cost of clarity | No | Yes | | Standard pattern (e.g., simple dict comprehension) | Yes | No | | Clever trick (e.g., tuple sort keys, walrus operator chains) | No | Yes |
Heuristics:
:=) in comprehension conditions is usually too clever<eafp_principle>
Python culture prefers EAFP (i.e., try/except) over LBYL (i.e., check-then-act). Use EAFP for dict lookups, file access, network calls, and anywhere race conditions matter. Use LBYL for pre-flight validation before expensive operations and performance-critical tight loops where the exceptional case is common.
Staff insight: EAFP is about correctness and clarity, not performance. The file existence check has a race condition; the exception handling doesn't. But exceptions have real cost (e.g., stack unwinding, traceback construction) — they're fine when the exceptional case is rare, not for expected control flow in loops. </eafp_principle>
<duck_typing>
Prefer protocols (i.e., behavior) over explicit type checking. Don't use isinstance except with abstract base classes at system boundaries. Use typing.Protocol for duck-typed interfaces — it gives you structural subtyping with type checker support. Use @runtime_checkable only when you genuinely need runtime protocol checking.
</duck_typing>
</core_philosophy>
<safety_constraints>
None sentinel patternglobal for shared state — use classes or explicit parameter passingException or bare except: and swallow errors silentlyeval() or exec() on untrusted inputrequirements.txt or setup.py for new projects — use pyproject.toml with uvtyping.Optional[X] when targeting Python 3.10+ — use X | None insteadexcept: without an exception typefrom __future__ import annotations in new code — it's superseded by PEP 649 (i.e., deferred evaluation, default in 3.14)with) for file handles, locks, and database connectionsif __name__ == "__main__": guard in executable scripts<fundamental_principles> <comprehensions>
List/dict/set comprehensions are Pythonic for simple transformations.
Complexity limits:
for clause: usually finefor clauses: acceptable for obvious Cartesian productsfor clauses: use explicit loops:=) in conditions: almost always too cleverStaff insight: Comprehensions are readable when they fit on one line and scan left-to-right. The moment you nest, chain conditions, or use walrus operators, you're optimizing for concision over clarity. </comprehensions>
<context_managers>
Always use with for files, locks, and database connections. Use contextlib.contextmanager for simple cases, ExitStack for dynamic resource management. Don't write try/finally when a context manager expresses intent better.
</context_managers>
<iterators_generators>
Use generators for large/infinite sequences and one-pass iteration. Use lists when you need multiple passes, random access, or length upfront. Generator expressions in function calls avoid intermediate lists: sum(x**2 for x in numbers).
</iterators_generators>
<mutable_defaults>
Default arguments are evaluated once at function definition. Use None as a sentinel for mutable defaults:
def append_to(element, to=None):
if to is None:
to = []
to.append(element)
return to
</mutable_defaults> </fundamental_principles>
<type_system>
Type hints are required for all production code. They provide explicit, machine-readable contracts; enable static analysis; and are critical for LLM-assisted development.
Where to use type hints:
<modern_type_syntax>
Union types (3.10+): Use X | None instead of Optional[X]. Use int | str instead of Union[int, str].
Type parameter syntax (3.12+, PEP 695): Use the bracket syntax for generics:
# Modern (3.12+)
def first[T](items: list[T]) -> T: ...
type Vector[T] = list[T]
# Legacy (pre-3.12) — don't use in new code targeting 3.12+
from typing import TypeVar
T = TypeVar('T')
def first(items: list[T]) -> T: ...
Deferred annotations (3.14+, PEP 649/749): Annotations are now evaluated lazily by default. from __future__ import annotations (PEP 563) is superseded — don't use it in new code. It still works but has different semantics: PEP 563 stringifies annotations, while PEP 649 stores an evaluator function. For code targeting 3.10-3.13, PEP 563 remains useful for forward references.
Other useful features:
TypedDict for structured dictionariesProtocol for structural subtyping (i.e., duck typing with types)ParamSpec and Concatenate for higher-order functionstyping.assert_never for exhaustiveness checking in match statements@override decorator (3.12+) for explicit method overriding
</modern_type_syntax><type_checkers>
ty (Astral): Preferred for most projects. Written in Rust; dramatically faster than alternatives. Currently in beta (v0.0.x) but effective at catching real bugs in typical codebases. Use ty unless you need Pydantic or Django ORM integration, which it doesn't yet support.
mypy: The reference implementation with the broadest ecosystem support. Use mypy when you need its plugin API for dynamic frameworks (e.g., Pydantic, SQLAlchemy, Django ORM) or when your project already uses it. Configure with strict = true in pyproject.toml.
pyright/basedpyright: The dominant IDE type checker (powers Pylance in VS Code). Richer type narrowing than mypy; checks unannotated code by default. Consider basedpyright for LSP integration in non-VS Code editors.
| Context | Recommendation |
|---------|---------------|
| New project, no Pydantic/Django | ty |
| Pydantic or Django ORM | mypy (with framework plugin) |
| IDE/LSP experience | pyright or basedpyright |
| Existing mypy project | Keep mypy |
| Maximum strictness in CI | mypy --strict or basedpyright |
</type_checkers>
</type_system>
<documentation_requirements>
Python documentation uses Sphinx with reStructuredText. Comprehensive documentation is mandatory for all production code.
Every module: Module-level docstring explaining purpose and main components.
Every public class: Class docstring with purpose, responsibilities, and invariants.
Every public function/method:
:param: and :type: (when :type: adds constraints beyond the signature):returns: and :rtype::raises:Every test: Docstring explaining what behavior is being tested and why.
See references/docstring-example.py for a complete example.
<docstring_type_annotations>
:type: AnnotationsInclude :type: alongside type hints only when it adds information beyond the signature:
| Situation | Include :type:? | Example |
|-----------|------------------|---------|
| Type hint says int, no constraints | No | Signature suffices |
| Parameter must be positive | Yes | :type: int (must be positive) |
| Accepts specific enum values | Yes | :type: str ("json" or "xml") |
| Complex generic with usage notes | Yes | Explain expected structure |
| Simple str, bool, list[str] | No | Signature suffices |
Omit :type: when it would mechanically duplicate the signature. The goal is useful documentation, not ceremony.
</docstring_type_annotations>
<docstring_style_choice>
For new projects, use Sphinx/reST style (:param:, :type:, :returns:, :rtype:, :raises:). For existing projects, follow the established convention — don't convert a Google-style codebase to Sphinx mid-project. pydoclint supports all three styles (sphinx, google, numpy); configure it to match your project.
</docstring_style_choice>
</documentation_requirements>
<python_tooling>
Ruff (linting and formatting):
ruff check for linting, ruff format for Black-compatible formattingS rule set) for common security checkspyproject.toml under [tool.ruff]pydoclint (docstring linting):
pydoclint[flake8])pyproject.toml under [tool.pydoclint]Bandit (security linting — when needed):
S rules cover most common security checks; use standalone Bandit only for security-critical projects needing full coverage (e.g., cryptographic vulnerability detection, severity classifications)pyproject.tomlType checker: See <type_checkers> — use ty, mypy, or pyright depending on your project's needs.
<project_management>
uv (primary project tool):
uv init), dependency management (uv add/uv remove), lockfiles (uv.lock), building (uv build), publishing (uv publish), and script execution (uv run)Hatch (matrix testing):
[[tool.hatch.envs.hatch-test.matrix]]
python = ["3.12", "3.13", "3.14"]
Then hatch test --all runs tests across all versions locally.hatch-vcs) and custom build hooks if needed| Workflow | Tool |
|----------|------|
| New project setup | uv init --package |
| Add dependencies | uv add <pkg> |
| Run tests | uv run pytest |
| Build and publish | uv build && uv publish |
| Local multi-version testing | Hatch (hatch test --all) |
| CI matrix testing | uv + GitHub Actions matrix |
</project_management>
NEVER guess or assume dependency versions. Verify current versions on PyPI before adding any dependency.
| Constraint | When to Use |
|------------|-------------|
| >=MAJOR.MINOR | Default — allows patch updates, guards against old bugs |
| >=MAJOR.MINOR,<NEXT_MAJOR | When major version breaks are likely |
| ==EXACT | Avoid — use lock files for reproducibility instead |
Staff insight: LLMs confidently hallucinate version numbers. The cost of a PyPI search is trivial compared to debugging phantom compatibility issues. This is especially critical for fast-moving ecosystems where major versions ship monthly.
See references/pyproject-example.toml for a starter project configuration.
</python_tooling>
<testing_ecosystem> For general testing philosophy and TDD principles, see the test-driven-development skill. This section covers Python-specific practices.
Use pytest for all new projects. It's the community standard: plain functions, simple assert with introspection, powerful fixtures, rich plugin ecosystem. Use unittest only in existing codebases that already use it.
Core testing principle (restated from test-driven-development skill): Mock at architectural boundaries (e.g., external systems, injected dependencies), not internal implementation details. The unittest.mock module is still useful with pytest.
</testing_ecosystem>
<async_patterns> Use async/await for I/O-bound concurrency with many concurrent connections (e.g., web servers, websockets, batch HTTP requests). Don't use it for CPU-bound tasks, simple scripts, or when overhead isn't justified.
Common mistakes:
requests or psycopg2)Bridge sync-to-async with asyncio.to_thread() (3.9+) when needed for CPU-bound work in async contexts.
Staff insight: Start with synchronous code. Add threads for I/O concurrency. Move to async only when you have many concurrent connections and measurable evidence it helps. </async_patterns>
<modern_python> <python_312_features>
def func[T](x: T) -> T) — see <modern_type_syntax>@override decorator: Explicit intent for method overriding, caught by type checkersdistutils removed from stdlib; use uv build
</python_312_features><python_313_features>
--disable-gil build flag; ~40% single-threaded overhead in this versionlocals() semantics change: Now returns a copy, not a proxy to the frame
</python_313_features><python_314_features>
<modern_type_syntax>t"Hello {name}") — evaluate to Template objects instead of str, enabling safe SQL interpolation, HTML templating, and structured loggingexcept clauses no longer require parentheses for multiple exceptions: except TimeoutError, ConnectionError:concurrent.interpreters — multiple interpreters per process with InterpreterPoolExecutorpdb can attach to running processes
</python_314_features><gil_and_concurrency>
Python's Global Interpreter Lock historically prevented true parallelism for CPU-bound threads. This is changing.
Current state (3.14):
multiprocessingPractical guidance today:
asyncio or threads (both work with or without GIL)multiprocessing remains the safe default; free-threaded builds are viable for early adoptersasyncio.to_thread() (3.9+) bridges sync code into async contextsStaff insight: Free-threading is the future but ecosystem support (e.g., C extensions, third-party libraries) is still maturing. For most projects, multiprocessing for CPU-bound work and threads/async for I/O-bound work remain the pragmatic choices.
</gil_and_concurrency>
</modern_python>
<python_fitness> Python excels at: Rapid prototyping, scripting and automation, data analysis (e.g., NumPy, pandas), web services (e.g., FastAPI, Django), and education.
Python struggles with: Performance-critical tight loops (use NumPy or drop to C/Rust), real-time systems, mobile development, systems programming, and CPU-bound parallelism (though free-threading is changing this).
Staff insight: Use Python where its strengths (e.g., development speed, ecosystem, readability) outweigh its weaknesses. Don't force it into low-level, high-performance, or mobile domains. </python_fitness>
<common_pitfalls> <common_mistakes>
<from_java> From Java:
<from_c> From C/C++:
<from_javascript> From JavaScript:
<python_specific_pitfalls>
lambda i=i: i) or functools.partial to capture values in loop-generated functionsNone, and False are all falsy. Use is None when checking for None specifically, not not xglobal abuse: Pass parameters explicitly or use classes. functools.lru_cache replaces most caching-via-global patternsTYPE_CHECKING guard for type-only imports; restructure modules to break real circular dependenciespathlib neglect: Prefer pathlib.Path over os.path for path manipulation — it's more readable and less error-pronedef for named functions
</python_specific_pitfalls>
</common_pitfalls><data_classes>
Use dataclasses (3.7+) for simple data containers. Use slots=True (3.10+) for memory efficiency and faster attribute access. Use frozen=True for immutability. Use attrs when you need validators, converters, or evolve() — its define/field API is more powerful than dataclasses for complex cases.
Dataclasses aren't a replacement for all classes — they're for data-focused classes. Use them for configuration, API responses, and value objects. Don't shoehorn behavior-heavy classes into dataclasses. </data_classes>
Tooling:
Style Guides:
Python programming emphasizes:
:type: duplicationAll new projects must use uv for project management, with Ruff, pydoclint, and a type checker (ty or mypy) enabled in CI/CD. Do NOT use darglint (archived 2022). Type hints and documentation are mandatory for all production code (exception: throwaway scripts). Follow existing project conventions rather than fighting established codebases.
</summary>data-ai
--- name: interactive-research description: Multi-source research orchestrated across a persistent agent team. Use for queries asking for research-style investigation of a topic — comparing, contrasting, surveying, investigating, evaluating, deeply researching, doing a literature review, or any similar research intent — or that ask what people are saying publicly about a topic (discourse, expert opinion, industry consensus, and related framings). Example phrasings (illustrative, not exhaustive):
development
Expert-level guidance for creating high-quality Claude Code skills. Use alongside Anthropic's skill-creator when creating new skills, improving existing skills, or needing guidance on skill content quality. Complements basic skill mechanics with research-driven content development, XML tag structuring, decision frameworks over mechanics, cross-references between skills, and systematic validation.
development
--- name: deep-research description: Deprecated name; the skill formerly here is now opinionated-research:interactive-research. Retained as a context-level notice because Claude Code now ships a built-in deep-research workflow that runs one-shot, non-interactive fan-out research (no conversation after the report). interactive-research instead runs a persistent agent team you can keep questioning once the report lands. Guidance for Claude: when the user makes a research request, ask whether they
development
Pedagogical framework for teaching programming through Socratic dialogue. Use when a learner wants to LEARN programming rather than have code written for them. Triggers include "teach me", "help me understand", "I'm learning", "tutor mode", or requests to not provide solutions. Emphasizes productive struggle, graduated hints, metacognitive scaffolding, and emotional support.