Refactoring & Code Transformations¶
BetterPy provides 7 refactoring intentions and actions — from populating function arguments to extracting protocols and converting between code patterns.
Arguments¶
Make parameter optional¶
Converts a required parameter to an optional one by adding a default value.
How to invoke: Alt+Enter → "BetterPy: Make optional"
Make parameter mandatory¶
Converts an optional parameter to a required one by removing the default value.
How to invoke: Alt+Enter → "BetterPy: Make mandatory"
Parameter Object¶
Parameter object refactoring¶
Introduce Parameter Object¶
Groups multiple function parameters into a single dataclass.
How to invoke: Refactor menu → "BetterPy: Introduce Parameter Object…"
Inline Parameter Object¶
The inverse of Introduce — inlines a parameter object back into individual parameters.
How to invoke: Refactor menu → "BetterPy: Inline Parameter Object…"
Parameter object gutter icon¶
Shows a gutter icon on functions that are candidates for the Introduce Parameter Object refactoring.
Parameter object MCP tool¶
Enables MCP (Model Context Protocol) tools for parameter object refactoring, allowing AI assistants to invoke it programmatically.
Code Transformations¶
Change visibility¶
Toggle a Python symbol between public and private naming without manually renaming the declaration and every call site. BetterPy reuses the IntelliJ rename refactoring, so all references in the project are updated together with the declaration.
How to invoke¶
Place the caret on the name of a function, class, or top-level/class-level variable, then Alt+Enter → "Change visibility: make private" or "Change visibility: make public".
The intention text adapts to the current name:
- A name without a leading underscore offers "make private" and renames name → _name.
- A name starting with _ offers "make public" and strips leading underscores (_name → name, __name → name).
Example¶
What it supports¶
- Functions, classes, and assignment targets (module-level and class-level attributes).
- Updates every reference in the project via the standard rename refactoring, so imports and qualified usages stay in sync.
- Works on names with one or more leading underscores; making a name public removes all of them.
What it deliberately avoids¶
- Dunder names such as
__init__or__call__— these have language-defined semantics. - Pytest test functions (
test_*) and test classes (Test*), since renaming them would hide them from collection. - Symbols defined in
conftest.py, where the_prefix would change pytest fixture discovery. - Names whose target form would collide with a Python reserved keyword.
- Files outside your own project (library or stub sources).
Notes¶
- The intention is gated by the Change visibility setting under Settings | BetterPy | Refactoring & code transformations | Code Transformations and is enabled by default.
- Because the rename runs through
RenameProcessor, conflicts (for example an existing symbol with the target name) are surfaced through the standard refactoring conflict dialog rather than silently overwriting code.
Dict access conversion¶
This feature bundles three related intentions that help you switch between the most common dictionary lookup styles depending on whether you want terse code, explicit fallback handling, or exception-driven control flow.
All three intentions only activate for expressions BetterPy can verify as mappings. That keeps the feature from appearing on unrelated subscription syntax such as lists, tuples, or arbitrary objects that just happen to define a get() method.
Convert bracket access to .get()¶
How to invoke: Alt+Enter on mapping[key] → "BetterPy: Replace 'dict[key]' with 'dict.get(key)'"
Use this when a missing key should quietly yield None instead of raising KeyError.
becomes:
What it supports¶
- Ordinary dictionary and mapping lookups such as
payload["id"]. - Variable keys like
payload[key_name]. - Tuple keys, including the comma form
mapping[a, b], which is rewritten tomapping.get((a, b)). - Complex mapping expressions such as
factory().cache["users"]. - Inline comments and spacing inside the brackets; BetterPy preserves the bracket contents instead of rebuilding them from scratch.
What it deliberately avoids¶
- Assignment targets like
mapping[key] = value. - Deletions such as
del mapping[key]. - Augmented assignments like
mapping[key] += 1. - Non-mapping operands such as lists.
Those cases are skipped because .get() changes the meaning: it reads a value, but it cannot stand in for write/delete syntax.
Convert .get() to bracket access¶
How to invoke: Alt+Enter on mapping.get(key) → "BetterPy: Replace 'dict.get(key)' with 'dict[key]'"
Use this when a key is expected to exist and surfacing KeyError is preferable to silently returning None.
becomes:
What it supports¶
- One-argument
.get()calls. - Positional keys such as
mapping.get(key). - Keyword style calls like
mapping.get(key=name), which becomemapping[name].
What it deliberately avoids¶
.get()calls with a default, for examplemapping.get("timeout", 30).- Calls with zero arguments or more than one effective argument.
- Calls on non-mapping values.
- Shadowed or unrelated
getcall sites where BetterPy cannot prove the qualifier is a mapping lookup.
This restriction matters because mapping.get(key, default) is not equivalent to mapping[key]; replacing it would drop the fallback value.
Convert .get(..., default) to try / except KeyError¶
How to invoke: Alt+Enter on mapping.get(key, default) used as a full assignment or return value → "BetterPy: Replace 'dict.get(key, default)' with try/except KeyError"
Use this when you want explicit exception-based control flow while keeping the same fallback result.
becomes:
It also works for direct returns:
becomes:
What it supports¶
- Full assignment statements with exactly one target.
- Full return statements.
- Positional and keyword forms for
keyanddefault. - Safe default expressions such as literals, constants, references, and simple constant-style subscriptions.
What it deliberately avoids¶
- Nested or partial expressions like
foo(payload.get("name", "unknown")). - Assignments with multiple targets.
- Calls without a default value.
- Defaults that may have side effects or are expensive to duplicate.
The generated try block repeats the lookup once and the fallback once, so BetterPy only offers the intention when that duplication is predictable and safe.
Convert try / except KeyError to .get()¶
How to invoke: Alt+Enter inside a qualifying try block → "BetterPy: Replace try-except with dict.get"
Use this when explicit exception handling is unnecessary and a compact lookup reads better.
becomes:
It also supports the common initialization-plus-pass pattern:
becomes:
What it supports¶
- A single
except KeyErrorclause. - A
tryblock containing exactly one statement. - Assignment or return forms.
- Fallbacks expressed as a literal or simple reference.
- Tuple keys and parenthesized complex mapping expressions.
What it deliberately avoids¶
trystatements withelseorfinallyblocks.- Multiple
exceptclauses. - Nested subscriptions such as
mapping[a][b], where.get()would not preserve the same failure boundary. - Complex fallback expressions.
These constraints ensure the shorter .get() form stays semantically equivalent to the original exception-handling code.
When to prefer each form¶
- Use
mapping[key]when the key is required and a missing value should fail loudly. - Use
mapping.get(key)when absence is expected andNoneis a meaningful signal. - Use
mapping.get(key, default)when you want a compact fallback. - Use
try/except KeyErrorwhen the fallback path needs to be visually explicit or is about to grow into more than a single expression.
Because BetterPy supports moving in both directions, you can start with the clearest form for the current situation and change it later without rewriting the statement by hand.