| name | enforcing-python-dunders |
| description | Ensure Python classes include appropriate, developer-friendly dunder methods—especially __repr__ and __str__. Use when: (1) Writing or modifying Python class definitions in .py files, (2) Refactoring existing Python classes, (3) Adding or improving __repr__, __str__, or natural arithmetic/comparison/container dunders. Do NOT use for: tests, fixtures, mocks, stubs, non-Python files (YAML, JSON, TOML), auto-generated code vendor libraries, or when user explicitly asks not to modify dunders. Ignored paths: tests/, *_test.py, test_*.py, .venv/, build/, dist/, migrations/, __init__.py. |
Python Dunder Method Enhancer
Overview
This skill ensures Python classes include appropriate, developer-friendly dunder methods. It prioritizes __repr__ and __str__ but also adds other dunder methods when they meaningfully improve code clarity and ergonomics.
Files and Directories to Ignore
This skill must NOT modify:
Test directories:
tests/,test/,__tests__/
Test file patterns:
*_test.py,test_*.py,conftest.py
Virtual environments:
.venv/,venv/,env/,.env/
Build artifacts and caches:
build/,dist/,egg-info/.mypy_cache/,.pytest_cache/,__pycache__/
Auto-generated and vendor code:
migrations/,alembic/- Schema files, codegen outputs
vendor/,third_party/,external/
Other:
__init__.py(unless user explicitly requests)
Core Guidelines
1. Prioritize __repr__ and __str__
- Always implement
__repr__: Provide a precise, unambiguous developer-oriented representation - Implement
__str__: Provide a readable, user-friendly representation - If only one can be implemented, implement
__repr__and let__str__fall back to it unless a distinct human-friendly format is genuinely needed
__repr__ requirements:
- Must be unambiguous and developer-focused
- Should ideally be valid Python that could recreate the object
- Format:
ClassName(attr1=value1, attr2=value2) - Use the ranking heuristics in
references/field_ranking_heuristic.mdwhen selecting which fields to include - Consult the reference to distinguish between high-signal and low-signal fields
- Ensure that large business models produce concise, useful representations (2-5 high-value fields)
__str__ requirements:
- User-friendly display format
- Can be less verbose than
__repr__ - Should be meaningful to end users
- Use the ranking heuristics in
references/field_ranking_heuristic.mdwhen selecting which fields to include - Start with the primary display name, add 1-2 short qualifiers
- Avoid raw IDs unless essential for user identification
2. Dunder Methods That Improve Intuition
Operator Methods
Implement only when they make objects more natural to use:
- Arithmetic:
__add__,__sub__,__mul__,__truediv__, etc. - Comparison:
__eq__,__lt__,__le__,__gt__,__ge__
Container-Like Behavior
Only when the object logically represents a collection:
__len__,__getitem__,__setitem__,__delitem__,__iter__,__contains__
Context Managers
Only when objects clearly manage a resource:
__enter__,__exit__
3. Avoid Overuse & Complexity
Do NOT implement dunder methods that:
- Introduce surprising behavior
- Make objects harder to reason about
- Obscure real meaning or side effects
Follow the principle of least astonishment.
4. Don't Call Dunder Methods Directly
When writing code that uses objects with dunders:
- Prefer
obj + otheroverobj.__add__(other) - Prefer
len(obj)overobj.__len__() - Prefer
obj[key]overobj.__getitem__(key)
5. Use functools.total_ordering When Appropriate
If the class implements:
__eq__, AND- Exactly ONE of:
__lt__,__le__,__gt__,__ge__
Then apply the @total_ordering decorator to generate the rest automatically.
6. Document Dunder Implementations
Every implemented dunder method must include:
- A concise docstring describing expectations
- Any edge-case behavior
- The reasoning when overriding default semantics
Forbidden Dunder Methods
NEVER implement these methods. They control Python internals, object lifecycle, memory, class creation, async protocol machinery, pickling machinery, or interpreter-level behaviors.
Absolutely Forbidden
__new____init_subclass____class_getitem____getnewargs____getnewargs_ex____getstate____setstate____reduce____reduce_ex____del____prepare____mro_entries__
Async Protocol (Forbidden)
__await____aiter____anext____aenter____aexit__
Descriptor Protocol (Forbidden)
__get____set____delete____set_name__
Attribute Interception (Forbidden)
__getattr____getattribute____setattr____delattr____dir__
Hashing & Identity (Forbidden)
__hash____bool__(too easy to misuse)
Additional Python Guidelines
Type Hints Mandatory
All dunder methods must include explicit type hints.
Prefer Immutability When Possible
Favour frozen=True dataclasses when mutation isn't required.
Use @dataclass When Appropriate
Let dataclasses supply basic dunder __init__/__eq__/__repr__ unless custom behavior is needed.
Dataclass Conversion Rules
Do NOT automatically convert an existing non-dataclass into a dataclass.
Only convert to @dataclass when ALL of these are true:
- The class is clearly a simple value object (fields only, no custom lifecycle)
- There is no inheritance, dynamic attributes, or metaclass use
- The user isn't relying on a custom
__init__that would be overwritten - The class has no
__slots__definition - There are no class-level validators or complex
__post_init__requirements
When in doubt, leave the class as-is and add dunder methods manually.
For new classes, prefer @dataclass when:
- The class is explicitly a value object, AND
- It only stores attributes without complex lifecycle or invariants, AND
- No inheritance or dynamic attributes are involved
__slots__ Rules
Never add __slots__ automatically.
Only add __slots__ when:
- The user explicitly requests it, AND
- The class restricts attributes intentionally
Truthiness and Hashing
__bool__ rules:
- Do NOT implement
__bool__unless the class has a single, obvious boolean meaning (e.g., success/failure wrapper, empty/non-empty collection) - Never infer truthiness from length, internal state, or "seems falsy" heuristics
- When in doubt, omit it entirely—let Python's default behavior apply
__hash__ rules:
- Never implement
__hash__for mutable classes - If it's not clearly immutable (e.g.,
frozen=Truedataclass, all attributes are read-only), assume it is mutable and leave__hash__alone - If you implement
__eq__without__hash__, Python automatically sets__hash__ = None(unhashable)—this is usually correct for mutable objects
Copy/Clone Behavior
Do not implement __copy__ or __deepcopy__ unless:
- The class manages external resources, OR
- The user explicitly requests custom clone semantics
No Side Effects in Representation Methods
__repr__ and __str__ must:
- Never mutate state
- Never perform I/O
- Never log
- Never compute expensive derived values
Use __post_init__ for Validation
Validate invariants early and clearly in dataclasses.
Subclassing Rules
When adding or modifying dunder methods on a subclass of a widely used class (standard library or popular third-party), be extra conservative:
1. Default: Inherit, Don't Override
If the parent class already defines a dunder method, do NOT override it unless:
- There is a clear, domain-specific need, AND
- The new behavior remains compatible with the parent's documented expectations
2. Don't Change Core Operation Meanings
Never change the fundamental semantics of:
- Equality and ordering:
__eq__,__lt__, etc. - Hashing and identity:
__hash__,__bool__ - Container behavior:
__len__,__getitem__,__contains__,__iter__
3. __repr__ and __str__ on Subclasses
It is usually safe to provide a more informative __repr__/__str__ on a subclass, as long as:
- They remain truthful and unambiguous
- They do not hide important information already shown by the parent
- They do not rely on side effects or heavy computation
- Prefer to call
super().__repr__()orsuper().__str__()and extend/augment the result
4. Always Call super() When Overriding
If the subclass overrides a dunder that the parent already uses internally:
- Call
super()when appropriate - Preserve any pre-/post-conditions the base class expects
5. No New "Fake" Container or Context Behavior
Do NOT add container dunders or context manager dunders to a subclass unless the parent class already has that role.
6. Check Documentation Before Changing
For classes from the standard library or well-known packages (dict, list, Path, BaseModel, DataFrame, etc.), treat their dunder behavior as part of a public contract.
7. When Unsure
If the correct behavior for a dunder on a subclass is unclear or potentially surprising:
- Avoid adding or modifying that dunder
- Leave a comment suggesting human review
Expected Output
When this skill makes changes, it should prefer:
- A unified diff patch (git-style) when editing files in place, OR
- A rewritten class definition with improved dunder methods, if the user is working in a single file snippet.
Avoid long narrative explanations unless explicitly requested by the user. Comments in code are acceptable when rationale is non-obvious.
Do NOT:
- Explain what dunder methods are
- Provide tutorial-style commentary
- Ask clarifying questions unless genuinely ambiguous
Do:
- Make the edit directly
- Add brief inline comments only where behavior is surprising or non-obvious
- Use type hints and docstrings as the primary documentation
When No Changes Are Needed
If a class already follows these dunder method guidelines, the skill should:
- Make no edits
- Return a brief confirmation (e.g., "Class already has appropriate dunder methods")
- Or output an empty diff
Do not rewrite existing correct code. Do not make style-only edits, reformat methods, or add unnecessary improvements when the class is already "good enough."
Examples (See references/)
Read these files before implementing dunder methods:
| File | Purpose |
|---|---|
references/examples/good_example.py |
Ideal classes with well-designed dunder methods. Pattern-match from these. |
references/examples/bad_example.py |
Anti-patterns to avoid. Check your work against these. |
references/examples/subclass_example.py |
How to handle subclasses of common library types (dict, Path, etc.). |
references/dunder_cheatsheet.md |
Quick reference for which dunders to implement, avoid, and how to reason about them. |
references/field_ranking_heuristic.md |
Guidelines for selecting high-value fields to include in __repr__ and __str__. |
When implementing dunders, open the relevant example file first.
Field Ranking Reference
This skill uses an additional bundled reference document:
references/field_ranking_heuristic.md
This reference describes the domain-agnostic field ranking rules used when selecting which attributes to include in generated __repr__ and __str__ methods.
When deciding which fields to display:
- Consult the reference file's priority ranking
- Favor identifiers, human-readable names, and canonical external handles
- Avoid printing large text fields, noisy metadata, or large collections
- Prefer short summaries for complex fields
The logic in this skill should defer to that reference whenever choosing or ranking fields for representations.
Quick Decision Tree
Is this Python source code (not tests/mocks/stubs)?
├─ No → Do NOT activate
└─ Yes → Continue
│
Does the class already have __repr__?
├─ No → Add __repr__ with type hints and docstring
└─ Yes → Check if it follows best practices
│
Should this class support comparison?
├─ Yes → Add __eq__ and one comparison op + @total_ordering
└─ No → Skip comparison dunders
│
Is this class container-like?
├─ Yes → Consider __len__, __getitem__, __iter__
└─ No → Do NOT add container dunders
│
Does this class manage resources?
├─ Yes → Consider __enter__, __exit__
└─ No → Do NOT add context manager dunders
Summary
When invoked, this skill should:
- Inspect Python class definitions
- Add beneficial dunder methods (
__repr__,__str__, and others when appropriate) - Avoid touching or generating forbidden dunder methods
- Improve clarity and Pythonic ergonomics
- Modify only Python source code—not tests
- Produce clean, idiomatic, documented results