Skip to content

Commit 28bc2e0

Browse files
docs: add encapsulation boundary rules (Principle 7)
Add new principle to PYTHON_BEST_PRACTICES.md addressing the anti-pattern of accessing private attributes via getattr(obj, '_internal', ...). This documents: - Why accessing private state is dangerous - Real example from PR #70 showing the anti-pattern - Correct approaches (expose public API, redesign) - Litmus test for detecting the problem Motivated by microsoft/amplifier-app-cli#70 which demonstrates the anti-pattern spreading across the codebase. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
1 parent a344a1c commit 28bc2e0

File tree

1 file changed

+40
-0
lines changed

1 file changed

+40
-0
lines changed

context/PYTHON_BEST_PRACTICES.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,46 @@ from .config import load_config
9898
- Group with blank lines between sections
9999
- Combine `from x import a, b` for related items only
100100

101+
### 7. Respect Encapsulation Boundaries
102+
103+
**Never access private attributes (`_name`) from outside a class.** Private attributes are implementation details that can change without notice.
104+
105+
| Pattern | Problem | Correct Approach |
106+
|---------|---------|------------------|
107+
| `getattr(obj, "_internal", None)` | Bypasses encapsulation | Request a public API |
108+
| `obj._private_attr` | Depends on implementation | Use protocol/interface |
109+
| Chained unwrapping: `getattr(obj, "_wrapped")._inner` | Fragile, compounds tech debt | Expose capability at top level |
110+
111+
**Why this matters**:
112+
- Private attributes change without deprecation warnings
113+
- Each "unwrap" pattern creates coupling to internal structure
114+
- Bugs become hard to trace through layers of private access
115+
- The "fix" creates more tech debt than the original problem
116+
117+
**Real anti-pattern** (from PR #70):
118+
```python
119+
# BAD: Reaching into private internals to find what you need
120+
bundle_resolver = getattr(resolver, "_bundle", resolver) # Unwrap layer 1
121+
activator = getattr(bundle_resolver, "_activator", None) # Unwrap layer 2
122+
```
123+
124+
**Correct approach**:
125+
```python
126+
# GOOD: If you need activator access, expose it via public API
127+
# Option 1: Add property to the wrapper
128+
# Option 2: Pass activator explicitly where needed
129+
# Option 3: Design interface to not need internal access
130+
activator = resolver.get_activator() # Public contract
131+
```
132+
133+
**When you feel tempted to access `_private`**:
134+
1. **Stop** - This is a design smell, not just a code smell
135+
2. **Ask**: Why isn't this exposed publicly?
136+
3. **If it should be public**: Add it to the protocol/interface
137+
4. **If it shouldn't**: You probably don't need it; redesign the approach
138+
139+
**Test**: *"Would this code break if the internal implementation changed?"* If yes, you're depending on internals.
140+
101141
---
102142

103143
## The Golden Rule

0 commit comments

Comments
 (0)