python-skills 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_skills/__init__.py +10 -0
- python_skills/__main__.py +6 -0
- python_skills/adapters/__init__.py +48 -0
- python_skills/adapters/agent_skills.py +415 -0
- python_skills/adapters/aider_adapter.py +226 -0
- python_skills/adapters/base.py +153 -0
- python_skills/adapters/claude.py +474 -0
- python_skills/adapters/cline.py +332 -0
- python_skills/adapters/codex.py +24 -0
- python_skills/adapters/continue_adapter.py +198 -0
- python_skills/adapters/cursor.py +327 -0
- python_skills/adapters/gemini.py +26 -0
- python_skills/adapters/goose.py +26 -0
- python_skills/adapters/junie.py +25 -0
- python_skills/adapters/kiro.py +382 -0
- python_skills/adapters/opencode.py +27 -0
- python_skills/adapters/roo.py +25 -0
- python_skills/adapters/universal.py +203 -0
- python_skills/adapters/vscode.py +27 -0
- python_skills/adapters/windsurf.py +26 -0
- python_skills/adapters/zed.py +27 -0
- python_skills/cli.py +326 -0
- python_skills/config.py +160 -0
- python_skills/detector.py +152 -0
- python_skills/installer.py +163 -0
- python_skills/markers.py +115 -0
- python_skills/skills/__init__.py +14 -0
- python_skills/skills/loader.py +171 -0
- python_skills/skills/metadata.py +152 -0
- python_skills/skills/registry.py +101 -0
- python_skills/state.py +204 -0
- python_skills-1.0.0.dist-info/METADATA +99 -0
- python_skills-1.0.0.dist-info/RECORD +105 -0
- python_skills-1.0.0.dist-info/WHEEL +4 -0
- python_skills-1.0.0.dist-info/entry_points.txt +2 -0
- python_skills-1.0.0.dist-info/licenses/LICENSE +21 -0
- skills/advanced_python.md +239 -0
- skills/anti_patterns/index.md +406 -0
- skills/comprehensions.md +167 -0
- skills/control_flow.md +175 -0
- skills/data_structures.md +243 -0
- skills/debugging/common_bugs.md +222 -0
- skills/debugging/inspection_techniques.md +249 -0
- skills/debugging/root_cause.md +203 -0
- skills/engineering/application_logging.md +195 -0
- skills/engineering/cli_apps.md +207 -0
- skills/engineering/configuration.md +218 -0
- skills/engineering/database.md +240 -0
- skills/engineering/dependency_management.md +205 -0
- skills/engineering/http_clients.md +267 -0
- skills/engineering/modules_packages.md +211 -0
- skills/engineering/packaging.md +197 -0
- skills/engineering/project_structure.md +155 -0
- skills/engineering/pyproject_toml.md +302 -0
- skills/engineering/virtual_environments.md +206 -0
- skills/functions.md +244 -0
- skills/generation/async_concurrency.md +291 -0
- skills/generation/error_handling.md +276 -0
- skills/generation/protocols_generics.md +243 -0
- skills/generation/type_hints.md +290 -0
- skills/generation/validation_pipeline.md +274 -0
- skills/generation/workflow.md +190 -0
- skills/oop.md +228 -0
- skills/quality/abstractions.md +154 -0
- skills/quality/comments.md +177 -0
- skills/quality/documentation.md +176 -0
- skills/quality/duplication.md +137 -0
- skills/quality/maintainability.md +142 -0
- skills/quality/naming.md +171 -0
- skills/quality/quality_functions.md +245 -0
- skills/quality/readability.md +239 -0
- skills/quality/type_annotations.md +192 -0
- skills/refactoring/behavior_preservation.md +157 -0
- skills/refactoring/incremental.md +187 -0
- skills/refactoring/interface_stability.md +199 -0
- skills/refactoring/safe_refactoring.md +206 -0
- skills/security/auth_boundaries.md +200 -0
- skills/security/command_injection.md +207 -0
- skills/security/dependency_risks.md +282 -0
- skills/security/file_handling.md +156 -0
- skills/security/input_validation.md +190 -0
- skills/security/path_traversal.md +172 -0
- skills/security/secrets.md +171 -0
- skills/security/sql_injection.md +188 -0
- skills/security/unsafe_deserialization.md +164 -0
- skills/stdlib/argparse.md +178 -0
- skills/stdlib/collections.md +212 -0
- skills/stdlib/datetime.md +187 -0
- skills/stdlib/functools.md +238 -0
- skills/stdlib/itertools.md +183 -0
- skills/stdlib/json.md +162 -0
- skills/stdlib/logging.md +185 -0
- skills/stdlib/os_sys.md +184 -0
- skills/stdlib/pathlib.md +218 -0
- skills/stdlib/re.md +171 -0
- skills/stdlib/statistics.md +112 -0
- skills/stdlib/subprocess.md +211 -0
- skills/testing/async_tests.md +249 -0
- skills/testing/coverage.md +168 -0
- skills/testing/edge_cases.md +197 -0
- skills/testing/fixtures_mocks.md +203 -0
- skills/testing/organization.md +205 -0
- skills/testing/parameterized.md +174 -0
- skills/testing/regression_tests.md +165 -0
- skills/variables_types.md +107 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Security: Unsafe Deserialization Prevention
|
|
2
|
+
|
|
3
|
+
**Purpose**: Prevent code execution via deserialization of untrusted data.
|
|
4
|
+
|
|
5
|
+
**When to use**: Loading pickles, YAML, JSON with custom decoders, any serialized data.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Never Deserialize Untrusted Data with Unsafe Formats
|
|
12
|
+
```python
|
|
13
|
+
# NEVER — pickle executes arbitrary code
|
|
14
|
+
import pickle
|
|
15
|
+
data = pickle.loads(untrusted_bytes) # RCE!
|
|
16
|
+
|
|
17
|
+
# NEVER — yaml.load without SafeLoader
|
|
18
|
+
import yaml
|
|
19
|
+
data = yaml.load(untrusted_string) # RCE!
|
|
20
|
+
|
|
21
|
+
# NEVER — shelve (uses pickle)
|
|
22
|
+
import shelve
|
|
23
|
+
db = shelve.open(untrusted_file)
|
|
24
|
+
|
|
25
|
+
# NEVER — dill, cloudpickle, marshal
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Safe Alternatives
|
|
29
|
+
| Unsafe | Safe Replacement |
|
|
30
|
+
|--------|------------------|
|
|
31
|
+
| `pickle` | `json`, `msgpack`, `orjson`, `cbor2` |
|
|
32
|
+
| `yaml.load()` | `yaml.safe_load()` |
|
|
33
|
+
| `shelve` | `sqlite3` + `json` |
|
|
34
|
+
| Custom `__reduce__` | Don't accept serialized objects |
|
|
35
|
+
|
|
36
|
+
### If You Must Use Pickle (Internal Only)
|
|
37
|
+
```python
|
|
38
|
+
# ONLY for trusted internal data
|
|
39
|
+
# Add integrity verification
|
|
40
|
+
import hmac
|
|
41
|
+
import hashlib
|
|
42
|
+
|
|
43
|
+
def sign_data(data: bytes, key: bytes) -> bytes:
|
|
44
|
+
return hmac.new(key, data, hashlib.sha256).digest()
|
|
45
|
+
|
|
46
|
+
def verify_and_load(data: bytes, signature: bytes, key: bytes) -> Any:
|
|
47
|
+
if not hmac.compare_digest(sign(data, key), signature):
|
|
48
|
+
raise SecurityError("Invalid signature")
|
|
49
|
+
return pickle.loads(data) # Still risky if key compromised
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### YAML Safe Loading
|
|
53
|
+
```python
|
|
54
|
+
import yaml
|
|
55
|
+
|
|
56
|
+
# ALWAYS use safe_load
|
|
57
|
+
data = yaml.safe_load(untrusted_string)
|
|
58
|
+
|
|
59
|
+
# Or explicit SafeLoader
|
|
60
|
+
data = yaml.load(untrusted_string, Loader=yaml.SafeLoader)
|
|
61
|
+
|
|
62
|
+
# NEVER
|
|
63
|
+
data = yaml.load(untrusted_string) # Default Loader is unsafe!
|
|
64
|
+
data = yaml.load(untrusted_string, Loader=yaml.Loader) # Unsafe!
|
|
65
|
+
data = yaml.load(untrusted_string, Loader=yaml.FullLoader) # Still unsafe!
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### JSON Safety
|
|
69
|
+
```python
|
|
70
|
+
import json
|
|
71
|
+
|
|
72
|
+
# json module is SAFE — no code execution
|
|
73
|
+
data = json.loads(untrusted_string)
|
|
74
|
+
|
|
75
|
+
# But: watch for DoS (deeply nested, huge objects)
|
|
76
|
+
# Use limits if needed
|
|
77
|
+
import sys
|
|
78
|
+
json.loads(huge_string) # Can consume memory
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Custom Decoders (Risk)
|
|
82
|
+
```python
|
|
83
|
+
# Dangerous if hook executes code
|
|
84
|
+
def dangerous_hook(d):
|
|
85
|
+
if "__class__" in d:
|
|
86
|
+
return globals()[d["__class__"]](**d) # RCE!
|
|
87
|
+
return d
|
|
88
|
+
|
|
89
|
+
json.loads(untrusted, object_hook=dangerous_hook) # NEVER
|
|
90
|
+
|
|
91
|
+
# Safe: only transform data, no execution
|
|
92
|
+
def safe_hook(d):
|
|
93
|
+
if "created_at" in d:
|
|
94
|
+
d["created_at"] = datetime.fromisoformat(d["created_at"])
|
|
95
|
+
return d
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Decision Rules
|
|
101
|
+
|
|
102
|
+
| Format | Trusted Internal | Untrusted External |
|
|
103
|
+
|--------|------------------|-------------------|
|
|
104
|
+
| JSON | ✓ | ✓ (with size limits) |
|
|
105
|
+
| YAML (safe_load) | ✓ | ✓ |
|
|
106
|
+
| MessagePack | ✓ | ✓ |
|
|
107
|
+
| CBOR | ✓ | ✓ |
|
|
108
|
+
| Pickle | ✓ (with signing) | ✗ NEVER |
|
|
109
|
+
| Shelve | ✓ | ✗ |
|
|
110
|
+
| Custom pickle | ✓ (with signing) | ✗ NEVER |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Preferred Patterns
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
# Config loading — use safe format
|
|
118
|
+
def load_config(path: Path) -> Config:
|
|
119
|
+
if path.suffix == ".json":
|
|
120
|
+
return Config.model_validate(json.loads(path.read_text()))
|
|
121
|
+
elif path.suffix in (".yaml", ".yml"):
|
|
122
|
+
return Config.model_validate(yaml.safe_load(path.read_text()))
|
|
123
|
+
elif path.suffix == ".toml":
|
|
124
|
+
import tomllib
|
|
125
|
+
return Config.model_validate(tomllib.loads(path.read_text()))
|
|
126
|
+
else:
|
|
127
|
+
raise ValueError("Unsupported config format")
|
|
128
|
+
|
|
129
|
+
# Cache — use safe serialization
|
|
130
|
+
def cache_get(key: str) -> Any | None:
|
|
131
|
+
data = redis.get(key)
|
|
132
|
+
if data:
|
|
133
|
+
return json.loads(data)
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
def cache_set(key: str, value: Any, ttl: int) -> None:
|
|
137
|
+
redis.setex(key, ttl, json.dumps(value, default=json_default))
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Avoid
|
|
143
|
+
|
|
144
|
+
- `pickle` for any external data
|
|
145
|
+
- `yaml.load()` without `SafeLoader`
|
|
146
|
+
- Custom `object_hook` that instantiates classes
|
|
147
|
+
- Storing serialized objects in database for later deserialization
|
|
148
|
+
- Assuming "internal" data stays internal forever
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Validation Considerations
|
|
153
|
+
|
|
154
|
+
- `bandit` B301, B506 checks
|
|
155
|
+
- Dependency scan for pickle usage
|
|
156
|
+
- Penetration testing deserialization endpoints
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Related Skills
|
|
161
|
+
|
|
162
|
+
- `security/input_validation.md`
|
|
163
|
+
- `stdlib/json.md`
|
|
164
|
+
- `engineering/configuration.md`
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Stdlib: argparse
|
|
2
|
+
|
|
3
|
+
**Purpose**: Command-line argument parsing.
|
|
4
|
+
|
|
5
|
+
**When to use**: CLI applications. For simple scripts, consider `sys.argv` or `click`/`typer` (external).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Basic Setup
|
|
12
|
+
```python
|
|
13
|
+
import argparse
|
|
14
|
+
|
|
15
|
+
parser = argparse.ArgumentParser(
|
|
16
|
+
description="My CLI tool",
|
|
17
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter, # Shows defaults
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Positional argument
|
|
21
|
+
parser.add_argument("input", type=Path, help="Input file")
|
|
22
|
+
|
|
23
|
+
# Optional argument
|
|
24
|
+
parser.add_argument("-o", "--output", type=Path, help="Output file")
|
|
25
|
+
parser.add_argument("-v", "--verbose", action="count", default=0) # -v, -vv, -vvv
|
|
26
|
+
|
|
27
|
+
# Flag
|
|
28
|
+
parser.add_argument("--force", action="store_true")
|
|
29
|
+
|
|
30
|
+
# Choice
|
|
31
|
+
parser.add_argument("--format", choices=["json", "yaml", "txt"], default="json")
|
|
32
|
+
|
|
33
|
+
# Type with validation
|
|
34
|
+
def positive_int(s):
|
|
35
|
+
v = int(s)
|
|
36
|
+
if v <= 0:
|
|
37
|
+
raise argparse.ArgumentTypeError("Must be positive")
|
|
38
|
+
return v
|
|
39
|
+
|
|
40
|
+
parser.add_argument("--count", type=positive_int, default=10)
|
|
41
|
+
|
|
42
|
+
# Append (multiple values)
|
|
43
|
+
parser.add_argument("--tags", action="append", default=[])
|
|
44
|
+
|
|
45
|
+
# Mutually exclusive group
|
|
46
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
47
|
+
group.add_argument("--fast", action="store_true")
|
|
48
|
+
group.add_argument("--thorough", action="store_true")
|
|
49
|
+
|
|
50
|
+
# Subcommands
|
|
51
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
52
|
+
init_parser = subparsers.add_parser("init")
|
|
53
|
+
init_parser.add_argument("name")
|
|
54
|
+
run_parser = subparsers.add_parser("run")
|
|
55
|
+
run_parser.add_argument("--port", type=int, default=8000)
|
|
56
|
+
|
|
57
|
+
args = parser.parse_args()
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Namespace Access
|
|
61
|
+
```python
|
|
62
|
+
args.input # Path object
|
|
63
|
+
args.output
|
|
64
|
+
args.verbose # 0, 1, 2, 3
|
|
65
|
+
args.force # True/False
|
|
66
|
+
args.format # "json" | "yaml" | "txt"
|
|
67
|
+
args.command # "init" | "run"
|
|
68
|
+
args.name # For init subcommand
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Custom Types
|
|
72
|
+
```python
|
|
73
|
+
# Path validation
|
|
74
|
+
def existing_file(s):
|
|
75
|
+
path = Path(s)
|
|
76
|
+
if not path.is_file():
|
|
77
|
+
raise argparse.ArgumentTypeError(f"Not a file: {s}")
|
|
78
|
+
return path
|
|
79
|
+
|
|
80
|
+
parser.add_argument("file", type=existing_file)
|
|
81
|
+
|
|
82
|
+
# Comma-separated list
|
|
83
|
+
def csv_list(s):
|
|
84
|
+
return [x.strip() for x in s.split(",") if x.strip()]
|
|
85
|
+
|
|
86
|
+
parser.add_argument("--tags", type=csv_list, default=[])
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Subcommand Pattern (Recommended)
|
|
90
|
+
```python
|
|
91
|
+
def main():
|
|
92
|
+
parser = create_parser()
|
|
93
|
+
args = parser.parse_args()
|
|
94
|
+
|
|
95
|
+
# Dispatch to handler
|
|
96
|
+
handlers = {
|
|
97
|
+
"init": handle_init,
|
|
98
|
+
"run": handle_run,
|
|
99
|
+
}
|
|
100
|
+
return handlers[args.command](args)
|
|
101
|
+
|
|
102
|
+
def handle_init(args):
|
|
103
|
+
print(f"Initializing {args.name}")
|
|
104
|
+
|
|
105
|
+
def handle_run(args):
|
|
106
|
+
print(f"Running on port {args.port}")
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
sys.exit(main())
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Decision Rules
|
|
115
|
+
|
|
116
|
+
| Need | Pattern |
|
|
117
|
+
|------|---------|
|
|
118
|
+
| Required input | Positional argument |
|
|
119
|
+
| Optional with default | `--option` with `default` |
|
|
120
|
+
| Boolean flag | `action="store_true"` |
|
|
121
|
+
| Count verbosity | `action="count"` |
|
|
122
|
+
| Multiple values | `action="append"` or `nargs="*"` |
|
|
123
|
+
| Choice validation | `choices=[...]` |
|
|
124
|
+
| Custom validation | `type=callable` |
|
|
125
|
+
| Subcommands | `add_subparsers()` |
|
|
126
|
+
| Mutually exclusive | `add_mutually_exclusive_group()` |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Preferred Patterns
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
# Main entry point pattern
|
|
134
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
135
|
+
parser = argparse.ArgumentParser(
|
|
136
|
+
prog="mytool",
|
|
137
|
+
description="Description",
|
|
138
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
139
|
+
)
|
|
140
|
+
# ... add arguments
|
|
141
|
+
return parser
|
|
142
|
+
|
|
143
|
+
def main(argv: list[str] | None = None) -> int:
|
|
144
|
+
parser = create_parser()
|
|
145
|
+
args = parser.parse_args(argv)
|
|
146
|
+
return dispatch(args)
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
sys.exit(main())
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Avoid
|
|
155
|
+
|
|
156
|
+
- `sys.argv` parsing manually (error-prone)
|
|
157
|
+
- Global parser state
|
|
158
|
+
- Complex logic in `type=` callables (keep simple)
|
|
159
|
+
- Required optional arguments (confusing UX)
|
|
160
|
+
- Too many positional arguments (>2-3)
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Validation Considerations
|
|
165
|
+
|
|
166
|
+
- `parser.parse_args(["--help"])` exits 0
|
|
167
|
+
- Invalid args exit 2 with usage
|
|
168
|
+
- Test with `argv` parameter for unit testing
|
|
169
|
+
- `ArgumentDefaultsHelpFormatter` shows defaults in help
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Related Skills
|
|
174
|
+
|
|
175
|
+
- `engineering/cli_apps.md`
|
|
176
|
+
- `engineering/configuration.md`
|
|
177
|
+
- `stdlib/os_sys.md`
|
|
178
|
+
- `generation/type_hints.md`
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Stdlib: collections
|
|
2
|
+
|
|
3
|
+
**Purpose**: Specialized container datatypes beyond built-ins.
|
|
4
|
+
|
|
5
|
+
**When to use**: When built-in list/dict/set don't fit the problem.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### `namedtuple` / `NamedTuple`
|
|
12
|
+
```python
|
|
13
|
+
# Classic
|
|
14
|
+
from collections import namedtuple
|
|
15
|
+
Point = namedtuple("Point", ["x", "y"])
|
|
16
|
+
|
|
17
|
+
# Modern (Python 3.6+) — preferred
|
|
18
|
+
from typing import NamedTuple
|
|
19
|
+
class Point(NamedTuple):
|
|
20
|
+
x: float
|
|
21
|
+
y: float
|
|
22
|
+
|
|
23
|
+
# Usage
|
|
24
|
+
p = Point(1.0, 2.0)
|
|
25
|
+
p.x, p[0] # Both work
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- Immutable, hashable, lightweight
|
|
29
|
+
- NamedTuple supports type hints, methods, defaults
|
|
30
|
+
|
|
31
|
+
### `dataclass` vs `NamedTuple`
|
|
32
|
+
| Feature | `NamedTuple` | `@dataclass` |
|
|
33
|
+
|---------|--------------|--------------|
|
|
34
|
+
| Mutability | Immutable | Mutable (or `frozen=True`) |
|
|
35
|
+
| Methods | Yes | Yes |
|
|
36
|
+
| Defaults | Yes | Yes |
|
|
37
|
+
| Inheritance | Limited | Full |
|
|
38
|
+
| Performance | Slightly faster | Flexible |
|
|
39
|
+
|
|
40
|
+
### `defaultdict`
|
|
41
|
+
```python
|
|
42
|
+
from collections import defaultdict
|
|
43
|
+
|
|
44
|
+
# Auto-create missing keys
|
|
45
|
+
grouped = defaultdict(list)
|
|
46
|
+
for item in items:
|
|
47
|
+
grouped[key(item)].append(item)
|
|
48
|
+
|
|
49
|
+
# Counter pattern
|
|
50
|
+
counts = defaultdict(int)
|
|
51
|
+
for item in items:
|
|
52
|
+
counts[item] += 1
|
|
53
|
+
|
|
54
|
+
# Custom factory
|
|
55
|
+
def default_factory():
|
|
56
|
+
return {"count": 0, "items": []}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### `Counter`
|
|
60
|
+
```python
|
|
61
|
+
from collections import Counter
|
|
62
|
+
|
|
63
|
+
c = Counter(["a", "b", "a", "c", "b", "a"])
|
|
64
|
+
# Counter({'a': 3, 'b': 2, 'c': 1})
|
|
65
|
+
|
|
66
|
+
c.most_common(2) # [('a', 3), ('b', 2)]
|
|
67
|
+
c.total() # 6 (Python 3.10+)
|
|
68
|
+
c.elements() # Iterator over elements
|
|
69
|
+
c.update(["a", "d"]) # Add counts
|
|
70
|
+
c.subtract({"a": 1}) # Subtract counts
|
|
71
|
+
|
|
72
|
+
# Arithmetic
|
|
73
|
+
c1 + c2 # Add (keep positive)
|
|
74
|
+
c1 - c2 # Subtract (keep positive)
|
|
75
|
+
c1 & c2 # Intersection (min)
|
|
76
|
+
c1 | c2 # Union (max)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `deque` (Double-Ended Queue)
|
|
80
|
+
```python
|
|
81
|
+
from collections import deque
|
|
82
|
+
|
|
83
|
+
d = deque([1, 2, 3], maxlen=5) # Bounded (auto-discards old)
|
|
84
|
+
|
|
85
|
+
d.append(4) # Right
|
|
86
|
+
d.appendleft(0) # Left
|
|
87
|
+
d.pop() # Right
|
|
88
|
+
d.popleft() # Left
|
|
89
|
+
d.extend([5, 6])
|
|
90
|
+
d.extendleft([-1, -2])
|
|
91
|
+
d.rotate(1) # Rotate right
|
|
92
|
+
d.rotate(-1) # Rotate left
|
|
93
|
+
|
|
94
|
+
# Use cases: queue, stack, sliding window, BFS
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `OrderedDict`
|
|
98
|
+
```python
|
|
99
|
+
from collections import OrderedDict
|
|
100
|
+
|
|
101
|
+
# Python 3.7+: regular dict preserves insertion order
|
|
102
|
+
# OrderedDict still useful for:
|
|
103
|
+
# - move_to_end(key, last=True)
|
|
104
|
+
# - popitem(last=True) — LIFO
|
|
105
|
+
# - Equality considers order
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `ChainMap`
|
|
109
|
+
```python
|
|
110
|
+
from collections import ChainMap
|
|
111
|
+
|
|
112
|
+
# Layered mappings (config layers)
|
|
113
|
+
defaults = {"color": "blue", "size": 10}
|
|
114
|
+
user = {"size": 12}
|
|
115
|
+
env = {"color": "red"}
|
|
116
|
+
|
|
117
|
+
config = ChainMap(env, user, defaults)
|
|
118
|
+
config["color"] # "red" (first match)
|
|
119
|
+
config["size"] # 12
|
|
120
|
+
|
|
121
|
+
# Mutable — writes go to first mapping
|
|
122
|
+
config["new"] = "value" # Added to env
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `UserDict`, `UserList`, `UserString`
|
|
126
|
+
```python
|
|
127
|
+
from collections import UserDict
|
|
128
|
+
|
|
129
|
+
class ValidatedDict(UserDict):
|
|
130
|
+
def __setitem__(self, key, value):
|
|
131
|
+
if not isinstance(key, str):
|
|
132
|
+
raise TypeError("Keys must be strings")
|
|
133
|
+
super().__setitem__(key, value)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- Subclassable wrappers for built-in types
|
|
137
|
+
- Easier than inheriting from `dict`/`list`/`str` directly
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Decision Rules
|
|
142
|
+
|
|
143
|
+
| Need | Type |
|
|
144
|
+
|------|------|
|
|
145
|
+
| Immutable record with names | `NamedTuple` |
|
|
146
|
+
| Mutable record with methods | `@dataclass` |
|
|
147
|
+
| Auto-create missing keys | `defaultdict` |
|
|
148
|
+
| Count hashable items | `Counter` |
|
|
149
|
+
| Queue (FIFO) | `deque` |
|
|
150
|
+
| Stack (LIFO) | `list` or `deque` |
|
|
151
|
+
| Bounded buffer | `deque(maxlen=N)` |
|
|
152
|
+
| Sliding window | `deque(maxlen=N)` |
|
|
153
|
+
| Layered config | `ChainMap` |
|
|
154
|
+
| Custom dict behavior | `UserDict` |
|
|
155
|
+
| LRU cache | `functools.lru_cache` (not collections) |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Preferred Patterns
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
# Frequency analysis
|
|
163
|
+
def top_words(text: str, n: int = 10) -> list[tuple[str, int]]:
|
|
164
|
+
words = re.findall(r"\w+", text.lower())
|
|
165
|
+
return Counter(words).most_common(n)
|
|
166
|
+
|
|
167
|
+
# Sliding window average
|
|
168
|
+
def moving_avg(values: list[float], window: int) -> list[float]:
|
|
169
|
+
from collections import deque
|
|
170
|
+
d = deque(maxlen=window)
|
|
171
|
+
result = []
|
|
172
|
+
for v in values:
|
|
173
|
+
d.append(v)
|
|
174
|
+
if len(d) == window:
|
|
175
|
+
result.append(sum(d) / window)
|
|
176
|
+
return result
|
|
177
|
+
|
|
178
|
+
# Config with layers
|
|
179
|
+
def load_config() -> ChainMap:
|
|
180
|
+
return ChainMap(
|
|
181
|
+
os.environ, # Highest priority
|
|
182
|
+
read_yaml("config.yaml"),
|
|
183
|
+
DEFAULTS, # Lowest priority
|
|
184
|
+
)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Avoid
|
|
190
|
+
|
|
191
|
+
- `OrderedDict` when regular `dict` works (Python 3.7+)
|
|
192
|
+
- `namedtuple` (legacy) — use `NamedTuple` from `typing`
|
|
193
|
+
- `ChainMap` for deep nesting (only looks at first level)
|
|
194
|
+
- `deque` for random access (O(n)) — use `list`
|
|
195
|
+
- `Counter` for non-hashable items
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Validation Considerations
|
|
200
|
+
|
|
201
|
+
- Type checkers understand `NamedTuple`, `TypedDict` better than `namedtuple`
|
|
202
|
+
- `Counter` arithmetic returns new `Counter`
|
|
203
|
+
- `defaultdict` converts to regular `dict` via `dict(dd)`
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Related Skills
|
|
208
|
+
|
|
209
|
+
- `core/data_structures.md`
|
|
210
|
+
- `core/oop.md` (dataclasses)
|
|
211
|
+
- `stdlib/itertools.md`
|
|
212
|
+
- `generation/type_hints.md`
|