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,187 @@
|
|
|
1
|
+
# Stdlib: datetime
|
|
2
|
+
|
|
3
|
+
**Purpose**: Date and time handling with timezone awareness.
|
|
4
|
+
|
|
5
|
+
**When to use**: All date/time operations. Avoid `time` module for new code.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Key Types
|
|
12
|
+
```python
|
|
13
|
+
from datetime import datetime, date, time, timedelta, timezone, tzinfo
|
|
14
|
+
|
|
15
|
+
# date: year, month, day (no time, no tz)
|
|
16
|
+
# time: hour, minute, second, microsecond, tzinfo
|
|
17
|
+
# datetime: date + time + tzinfo
|
|
18
|
+
# timedelta: duration
|
|
19
|
+
# timezone: fixed offset tzinfo
|
|
20
|
+
# ZoneInfo (Python 3.9+): IANA timezone database
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Creation
|
|
24
|
+
```python
|
|
25
|
+
# date
|
|
26
|
+
date(2024, 1, 15)
|
|
27
|
+
date.fromisoformat("2024-01-15")
|
|
28
|
+
date.today()
|
|
29
|
+
|
|
30
|
+
# time
|
|
31
|
+
time(14, 30, 0)
|
|
32
|
+
time.fromisoformat("14:30:00")
|
|
33
|
+
|
|
34
|
+
# datetime (naive = no timezone)
|
|
35
|
+
datetime(2024, 1, 15, 14, 30, 0)
|
|
36
|
+
datetime.fromisoformat("2024-01-15T14:30:00")
|
|
37
|
+
datetime.now() # Local naive
|
|
38
|
+
datetime.utcnow() # DEPRECATED — avoid
|
|
39
|
+
|
|
40
|
+
# datetime (aware = with timezone)
|
|
41
|
+
datetime.now(timezone.utc)
|
|
42
|
+
datetime.now(ZoneInfo("America/New_York"))
|
|
43
|
+
datetime.fromisoformat("2024-01-15T14:30:00+00:00")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Timezone Handling (Critical)
|
|
47
|
+
```python
|
|
48
|
+
from zoneinfo import ZoneInfo # Python 3.9+
|
|
49
|
+
|
|
50
|
+
# UTC
|
|
51
|
+
utc = timezone.utc
|
|
52
|
+
dt_utc = datetime.now(utc)
|
|
53
|
+
|
|
54
|
+
# Named timezone
|
|
55
|
+
ny = ZoneInfo("America/New_York")
|
|
56
|
+
dt_ny = datetime.now(ny)
|
|
57
|
+
|
|
58
|
+
# Convert between timezones
|
|
59
|
+
dt_ny = dt_utc.astimezone(ny)
|
|
60
|
+
|
|
61
|
+
# Make naive aware (assume UTC)
|
|
62
|
+
dt_aware = dt_naive.replace(tzinfo=timezone.utc)
|
|
63
|
+
|
|
64
|
+
# Make aware naive (lose tz info)
|
|
65
|
+
dt_naive = dt_aware.replace(tzinfo=None)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Operations
|
|
69
|
+
```python
|
|
70
|
+
# Arithmetic
|
|
71
|
+
dt + timedelta(days=7)
|
|
72
|
+
dt - timedelta(hours=3)
|
|
73
|
+
dt1 - dt2 # Returns timedelta
|
|
74
|
+
|
|
75
|
+
# Comparison (only aware-aware or naive-naive)
|
|
76
|
+
dt1 < dt2
|
|
77
|
+
|
|
78
|
+
# Replace fields
|
|
79
|
+
dt.replace(year=2025, hour=0)
|
|
80
|
+
|
|
81
|
+
# Formatting
|
|
82
|
+
dt.isoformat() # "2024-01-15T14:30:00+00:00"
|
|
83
|
+
dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
84
|
+
dt.strftime("%Y-%m-%dT%H:%M:%S%z")
|
|
85
|
+
|
|
86
|
+
# Parsing
|
|
87
|
+
datetime.fromisoformat("2024-01-15T14:30:00+00:00") # Python 3.11+ handles all ISO
|
|
88
|
+
datetime.strptime("15/01/2024", "%d/%m/%Y")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Timedelta
|
|
92
|
+
```python
|
|
93
|
+
timedelta(days=7, hours=3, minutes=30)
|
|
94
|
+
timedelta(weeks=1)
|
|
95
|
+
timedelta.total_seconds() # Float seconds
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Decision Rules
|
|
101
|
+
|
|
102
|
+
| Situation | Type |
|
|
103
|
+
|-----------|------|
|
|
104
|
+
| Calendar date only | `date` |
|
|
105
|
+
| Time of day only | `time` |
|
|
106
|
+
| Timestamp (point in time) | `datetime` (aware!) |
|
|
107
|
+
| Duration | `timedelta` |
|
|
108
|
+
| Fixed offset | `timezone(timedelta(hours=5))` |
|
|
109
|
+
| Named timezone | `ZoneInfo("Region/City")` |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Preferred Patterns
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
# Always use aware datetimes for timestamps
|
|
117
|
+
def now_utc() -> datetime:
|
|
118
|
+
return datetime.now(timezone.utc)
|
|
119
|
+
|
|
120
|
+
# Parse ISO with fallback
|
|
121
|
+
def parse_iso(s: str) -> datetime:
|
|
122
|
+
try:
|
|
123
|
+
return datetime.fromisoformat(s)
|
|
124
|
+
except ValueError:
|
|
125
|
+
# Handle common variations
|
|
126
|
+
for fmt in ("%Y-%m-%dT%H:%M:%S%z", "%Y-%m-%dT%H:%M:%S.%f%z", "%Y-%m-%d"):
|
|
127
|
+
try:
|
|
128
|
+
return datetime.strptime(s, fmt)
|
|
129
|
+
except ValueError:
|
|
130
|
+
continue
|
|
131
|
+
raise
|
|
132
|
+
|
|
133
|
+
# Serialize for JSON/API
|
|
134
|
+
def serialize_dt(dt: datetime) -> str:
|
|
135
|
+
if dt.tzinfo is None:
|
|
136
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
137
|
+
return dt.isoformat()
|
|
138
|
+
|
|
139
|
+
# Deserialize from JSON/API
|
|
140
|
+
def deserialize_dt(s: str) -> datetime:
|
|
141
|
+
dt = parse_iso(s)
|
|
142
|
+
if dt.tzinfo is None:
|
|
143
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
144
|
+
return dt
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Avoid
|
|
150
|
+
|
|
151
|
+
- `datetime.utcnow()` (deprecated, returns naive)
|
|
152
|
+
- Naive datetimes for timestamps (ambiguous)
|
|
153
|
+
- `pytz` (use `zoneinfo` stdlib in 3.9+)
|
|
154
|
+
- String manipulation for date math
|
|
155
|
+
- Comparing aware to naive (raises TypeError)
|
|
156
|
+
- `time.mktime` / `time.gmtime` (use datetime methods)
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Python 3.11+ Improvements
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# fromisoformat handles all ISO 8601 formats
|
|
164
|
+
datetime.fromisoformat("2024-01-15T14:30:00+05:30")
|
|
165
|
+
datetime.fromisoformat("2024-01-15") # Returns date, not datetime!
|
|
166
|
+
|
|
167
|
+
# UTC shortcut
|
|
168
|
+
datetime.UTC # timezone.utc singleton
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Validation Considerations
|
|
174
|
+
|
|
175
|
+
- Always validate timezone on input
|
|
176
|
+
- Use `isinstance(dt, datetime)` not `type(dt) is datetime`
|
|
177
|
+
- `date` and `datetime` are not comparable
|
|
178
|
+
- `timedelta` has no months/years (variable length)
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Related Skills
|
|
183
|
+
|
|
184
|
+
- `stdlib/json.md` (serialization)
|
|
185
|
+
- `engineering/configuration.md`
|
|
186
|
+
- `security/input_validation.md`
|
|
187
|
+
- `generation/type_hints.md`
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Stdlib: functools
|
|
2
|
+
|
|
3
|
+
**Purpose**: Higher-order functions and function utilities.
|
|
4
|
+
|
|
5
|
+
**When to use**: Function composition, caching, decoration, partial application.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### `partial` / `partialmethod`
|
|
12
|
+
```python
|
|
13
|
+
from functools import partial, partialmethod
|
|
14
|
+
|
|
15
|
+
# Fix arguments
|
|
16
|
+
def power(base, exp):
|
|
17
|
+
return base ** exp
|
|
18
|
+
|
|
19
|
+
square = partial(power, exp=2)
|
|
20
|
+
square(5) # 25
|
|
21
|
+
|
|
22
|
+
# For methods (binds self)
|
|
23
|
+
class Math:
|
|
24
|
+
def __init__(self, factor):
|
|
25
|
+
self.factor = factor
|
|
26
|
+
def multiply(self, x):
|
|
27
|
+
return self.factor * x
|
|
28
|
+
double = partialmethod(multiply, 2)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### `wraps` (Critical for Decorators)
|
|
32
|
+
```python
|
|
33
|
+
from functools import wraps
|
|
34
|
+
|
|
35
|
+
def my_decorator(func):
|
|
36
|
+
@wraps(func) # Copies __name__, __doc__, __annotations__, __module__, __qualname__, __dict__
|
|
37
|
+
def wrapper(*args, **kwargs):
|
|
38
|
+
return func(*args, **kwargs)
|
|
39
|
+
return wrapper
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- **Always** use on decorator wrappers
|
|
43
|
+
- Without it: lost metadata, broken introspection, broken type hints
|
|
44
|
+
|
|
45
|
+
### `lru_cache` (Memoization)
|
|
46
|
+
```python
|
|
47
|
+
from functools import lru_cache
|
|
48
|
+
|
|
49
|
+
@lru_cache(maxsize=128) # None = unbounded
|
|
50
|
+
def fib(n: int) -> int:
|
|
51
|
+
if n < 2:
|
|
52
|
+
return n
|
|
53
|
+
return fib(n-1) + fib(n-2)
|
|
54
|
+
|
|
55
|
+
fib.cache_info() # CacheInfo(hits, misses, maxsize, currsize)
|
|
56
|
+
fib.cache_clear() # Clear cache
|
|
57
|
+
|
|
58
|
+
# Typed cache (Python 3.9+)
|
|
59
|
+
@lru_cache(maxsize=None, typed=True)
|
|
60
|
+
def typed_func(x: int) -> int: # Separate cache for int vs float
|
|
61
|
+
...
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- Thread-safe
|
|
65
|
+
- Only for pure functions (same args → same result)
|
|
66
|
+
- `maxsize` should be power of 2 for performance
|
|
67
|
+
|
|
68
|
+
### `cache` (Python 3.9+)
|
|
69
|
+
```python
|
|
70
|
+
from functools import cache
|
|
71
|
+
|
|
72
|
+
@cache
|
|
73
|
+
def expensive(x):
|
|
74
|
+
...
|
|
75
|
+
```
|
|
76
|
+
- Unbounded `lru_cache` (no maxsize limit)
|
|
77
|
+
- Simpler for "cache forever" cases
|
|
78
|
+
|
|
79
|
+
### `cached_property` (Python 3.8+)
|
|
80
|
+
```python
|
|
81
|
+
from functools import cached_property
|
|
82
|
+
|
|
83
|
+
class DataProcessor:
|
|
84
|
+
@cached_property
|
|
85
|
+
def processed(self) -> DataFrame:
|
|
86
|
+
return expensive_computation(self.raw)
|
|
87
|
+
```
|
|
88
|
+
- Computes once per instance, caches in `__dict__`
|
|
89
|
+
- Not thread-safe for simultaneous first access
|
|
90
|
+
|
|
91
|
+
### `singledispatch` / `singledispatchmethod`
|
|
92
|
+
```python
|
|
93
|
+
from functools import singledispatch, singledispatchmethod
|
|
94
|
+
|
|
95
|
+
@singledispatch
|
|
96
|
+
def serialize(obj):
|
|
97
|
+
raise NotImplementedError(f"Cannot serialize {type(obj)}")
|
|
98
|
+
|
|
99
|
+
@serialize.register
|
|
100
|
+
def _(obj: int) -> str:
|
|
101
|
+
return str(obj)
|
|
102
|
+
|
|
103
|
+
@serialize.register
|
|
104
|
+
def _(obj: list) -> str:
|
|
105
|
+
return "[" + ", ".join(serialize(x) for x in obj) + "]"
|
|
106
|
+
|
|
107
|
+
# For methods
|
|
108
|
+
class Formatter:
|
|
109
|
+
@singledispatchmethod
|
|
110
|
+
def format(self, value):
|
|
111
|
+
raise NotImplementedError
|
|
112
|
+
|
|
113
|
+
@format.register
|
|
114
|
+
def _(self, value: int) -> str:
|
|
115
|
+
return f"int: {value}"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `reduce`
|
|
119
|
+
```python
|
|
120
|
+
from functools import reduce
|
|
121
|
+
import operator
|
|
122
|
+
|
|
123
|
+
reduce(operator.add, [1, 2, 3, 4], 0) # 10 (with initial)
|
|
124
|
+
reduce(operator.mul, [1, 2, 3, 4]) # 24 (no initial)
|
|
125
|
+
reduce(lambda acc, x: acc + [x*2], items, [])
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- Left fold: `reduce(f, [a,b,c], init) = f(f(f(init, a), b), c)`
|
|
129
|
+
- Prefer explicit loops for readability in most cases
|
|
130
|
+
|
|
131
|
+
### `cmp_to_key`
|
|
132
|
+
```python
|
|
133
|
+
from functools import cmp_to_key
|
|
134
|
+
|
|
135
|
+
def compare(a, b):
|
|
136
|
+
return (a > b) - (a < b) # -1, 0, 1
|
|
137
|
+
|
|
138
|
+
sorted(items, key=cmp_to_key(compare))
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- Convert old-style comparison function to key function
|
|
142
|
+
- Needed for complex sorting not expressible as key
|
|
143
|
+
|
|
144
|
+
### `total_ordering`
|
|
145
|
+
```python
|
|
146
|
+
from functools import total_ordering
|
|
147
|
+
|
|
148
|
+
@total_ordering
|
|
149
|
+
class Version:
|
|
150
|
+
def __init__(self, major, minor, patch):
|
|
151
|
+
self.tuple = (major, minor, patch)
|
|
152
|
+
|
|
153
|
+
def __eq__(self, other):
|
|
154
|
+
return self.tuple == other.tuple
|
|
155
|
+
|
|
156
|
+
def __lt__(self, other):
|
|
157
|
+
return self.tuple < other.tuple
|
|
158
|
+
# Generates __le__, __gt__, __ge__, __ne__
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- Define `__eq__` + one of `__lt__`, `__le__`, `__gt__`, `__ge__`
|
|
162
|
+
- Generates the rest
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Decision Rules
|
|
167
|
+
|
|
168
|
+
| Need | Tool |
|
|
169
|
+
|------|------|
|
|
170
|
+
| Fix some arguments | `partial` / `partialmethod` |
|
|
171
|
+
| Cache pure function results | `lru_cache` / `cache` |
|
|
172
|
+
| Cache instance property | `cached_property` |
|
|
173
|
+
| Single-dispatch generic function | `singledispatch` |
|
|
174
|
+
| Single-dispatch method | `singledispatchmethod` |
|
|
175
|
+
| Reduce sequence to single value | `reduce` (or explicit loop) |
|
|
176
|
+
| Old-style comparison to key | `cmp_to_key` |
|
|
177
|
+
| Auto-generate comparisons | `total_ordering` |
|
|
178
|
+
| Preserve decorator metadata | `wraps` (ALWAYS) |
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Preferred Patterns
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
# Configurable retry with partial
|
|
186
|
+
retry_3 = partial(retry, times=3, delay=1.0)
|
|
187
|
+
retry_5 = partial(retry, times=5, delay=0.5)
|
|
188
|
+
|
|
189
|
+
# Cached property for expensive computation
|
|
190
|
+
class Service:
|
|
191
|
+
def __init__(self, config):
|
|
192
|
+
self.config = config
|
|
193
|
+
|
|
194
|
+
@cached_property
|
|
195
|
+
def client(self) -> APIClient:
|
|
196
|
+
return APIClient(self.config.api_key, timeout=self.config.timeout)
|
|
197
|
+
|
|
198
|
+
# Generic function for extensible serialization
|
|
199
|
+
@singledispatch
|
|
200
|
+
def to_json(obj) -> str:
|
|
201
|
+
raise TypeError(f"Type {type(obj)} not serializable")
|
|
202
|
+
|
|
203
|
+
@to_json.register
|
|
204
|
+
def _(obj: datetime) -> str:
|
|
205
|
+
return obj.isoformat()
|
|
206
|
+
|
|
207
|
+
@to_json.register
|
|
208
|
+
def _(obj: UUID) -> str:
|
|
209
|
+
return str(obj)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Avoid
|
|
215
|
+
|
|
216
|
+
- `lru_cache` on functions with side effects
|
|
217
|
+
- `lru_cache` on methods without `maxsize` (memory leak — instance never freed)
|
|
218
|
+
- `cached_property` with mutable return values (shared reference)
|
|
219
|
+
- `reduce` for simple aggregations (`sum`, `max`, `min`, `any`, `all`)
|
|
220
|
+
- `partial` with mutable default arguments
|
|
221
|
+
- Forgetting `@wraps` on decorators
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Validation Considerations
|
|
226
|
+
|
|
227
|
+
- `cache_info()` for cache effectiveness monitoring
|
|
228
|
+
- `typed=True` prevents `1` and `1.0` sharing cache entry
|
|
229
|
+
- Thread safety: `lru_cache` yes, `cached_property` no
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Related Skills
|
|
234
|
+
|
|
235
|
+
- `core/advanced_python.md` (decorators)
|
|
236
|
+
- `core/functions.md`
|
|
237
|
+
- `generation/async_concurrency.md` (async caching)
|
|
238
|
+
- `quality/functions.md`
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Stdlib: itertools
|
|
2
|
+
|
|
3
|
+
**Purpose**: Efficient iteration tools for combinatorics and data processing.
|
|
4
|
+
|
|
5
|
+
**When to use**: Complex iteration patterns, avoiding manual loops.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Infinite Iterators
|
|
12
|
+
```python
|
|
13
|
+
import itertools
|
|
14
|
+
|
|
15
|
+
itertools.count(start=0, step=1) # 0, 1, 2, ...
|
|
16
|
+
itertools.cycle(iterable) # Repeat forever
|
|
17
|
+
itertools.repeat(elem, times=None) # Repeat elem (forever if times=None)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Finite Iterators (Combinatorics)
|
|
21
|
+
```python
|
|
22
|
+
# Cartesian product
|
|
23
|
+
itertools.product("AB", repeat=2) # AA, AB, BA, BB
|
|
24
|
+
itertools.product(range(3), "AB") # (0,A), (0,B), (1,A), ...
|
|
25
|
+
|
|
26
|
+
# Permutations (order matters)
|
|
27
|
+
itertools.permutations("ABCD", 2) # AB, AC, AD, BA, BC, BD, CA, CB, CD, DA, DB, DC
|
|
28
|
+
|
|
29
|
+
# Combinations (order doesn't matter)
|
|
30
|
+
itertools.combinations("ABCD", 2) # AB, AC, AD, BC, BD, CD
|
|
31
|
+
itertools.combinations_with_replacement("ABCD", 2) # AA, AB, AC, AD, BB, BC, BD, CC, CD, DD
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Filtering Iterators
|
|
35
|
+
```python
|
|
36
|
+
itertools.filterfalse(pred, iterable) # Opposite of filter
|
|
37
|
+
itertools.takewhile(pred, iterable) # Until pred false
|
|
38
|
+
itertools.dropwhile(pred, iterable) # Skip while pred true
|
|
39
|
+
itertools.compress(data, selectors) # Data where selector true
|
|
40
|
+
itertools.islice(iterable, start, stop, step) # Slice iterator
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Grouping
|
|
44
|
+
```python
|
|
45
|
+
# Group consecutive items (input MUST be sorted by key)
|
|
46
|
+
itertools.groupby(iterable, key=lambda x: x[0])
|
|
47
|
+
# Returns (key, group_iterator) — consume group before next iteration!
|
|
48
|
+
|
|
49
|
+
# Example
|
|
50
|
+
data = [("a", 1), ("a", 2), ("b", 3), ("a", 4)]
|
|
51
|
+
for key, group in itertools.groupby(data, key=lambda x: x[0]):
|
|
52
|
+
print(key, list(group))
|
|
53
|
+
# a [(a,1), (a,2)]
|
|
54
|
+
# b [(b,3)]
|
|
55
|
+
# a [(a,4)] # Separate group!
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Accumulation
|
|
59
|
+
```python
|
|
60
|
+
itertools.accumulate(iterable, func=operator.add)
|
|
61
|
+
# Running totals: [1, 2, 3] -> 1, 3, 6
|
|
62
|
+
itertools.accumulate([1,2,3], func=operator.mul) # 1, 2, 6
|
|
63
|
+
itertools.accumulate([1,2,3], initial=10) # 10, 11, 13, 16 (Python 3.8+)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Merging/Transforming
|
|
67
|
+
```python
|
|
68
|
+
itertools.chain(*iterables) # Flatten: chain(a, b, c)
|
|
69
|
+
itertools.chain.from_iterable(iterable) # Flatten nested
|
|
70
|
+
itertools.zip_longest(*iterables, fillvalue=None) # Pad shorter
|
|
71
|
+
itertools.pairwise(iterable) # (s0,s1), (s1,s2), ... (Python 3.10+)
|
|
72
|
+
itertools.starmap(func, iterable) # func(*args) for each item
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Recipes (from docs)
|
|
76
|
+
```python
|
|
77
|
+
def batched(iterable, n):
|
|
78
|
+
"Batch data into tuples of length n. The last batch may be shorter."
|
|
79
|
+
if n < 1:
|
|
80
|
+
raise ValueError("n must be at least one")
|
|
81
|
+
it = iter(iterable)
|
|
82
|
+
while batch := tuple(itertools.islice(it, n)):
|
|
83
|
+
yield batch
|
|
84
|
+
|
|
85
|
+
def sliding_window(iterable, n):
|
|
86
|
+
"Return a sliding window of width n over the iterable."
|
|
87
|
+
it = iter(iterable)
|
|
88
|
+
window = collections.deque(itertools.islice(it, n-1), maxlen=n)
|
|
89
|
+
for x in it:
|
|
90
|
+
window.append(x)
|
|
91
|
+
yield tuple(window)
|
|
92
|
+
|
|
93
|
+
def roundrobin(*iterables):
|
|
94
|
+
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
|
|
95
|
+
num_active = len(iterables)
|
|
96
|
+
nexts = cycle(iter(it).__next__ for it in iterables)
|
|
97
|
+
while num_active:
|
|
98
|
+
try:
|
|
99
|
+
for next_func in nexts:
|
|
100
|
+
yield next_func()
|
|
101
|
+
except StopIteration:
|
|
102
|
+
num_active -= 1
|
|
103
|
+
nexts = cycle(itertools.islice(nexts, num_active))
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Decision Rules
|
|
109
|
+
|
|
110
|
+
| Need | Tool |
|
|
111
|
+
|------|------|
|
|
112
|
+
| All combinations | `product` |
|
|
113
|
+
| Ordered selections | `permutations` |
|
|
114
|
+
| Unordered selections | `combinations` |
|
|
115
|
+
| Unordered with replacement | `combinations_with_replacement` |
|
|
116
|
+
| Skip prefix | `dropwhile` / `islice` |
|
|
117
|
+
| Take prefix | `takewhile` / `islice` |
|
|
118
|
+
| Group consecutive | `groupby` (sort first!) |
|
|
119
|
+
| Running totals | `accumulate` |
|
|
120
|
+
| Flatten sequences | `chain` / `chain.from_iterable` |
|
|
121
|
+
| Parallel iteration with padding | `zip_longest` |
|
|
122
|
+
| Adjacent pairs | `pairwise` (3.10+) |
|
|
123
|
+
| Batch processing | `batched` recipe / `itertools.batched` (3.12+) |
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Preferred Patterns
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# Chunk processing
|
|
131
|
+
for batch in batched(large_iterable, 1000):
|
|
132
|
+
process_batch(batch)
|
|
133
|
+
|
|
134
|
+
# Pairwise comparison
|
|
135
|
+
for a, b in itertools.pairwise(sorted_data):
|
|
136
|
+
if b - a > threshold:
|
|
137
|
+
...
|
|
138
|
+
|
|
139
|
+
# Cartesian product for parameter grids
|
|
140
|
+
for params in itertools.product(learning_rates, batch_sizes, optimizers):
|
|
141
|
+
train(*params)
|
|
142
|
+
|
|
143
|
+
# Consuming groupby correctly
|
|
144
|
+
for key, group in itertools.groupby(sorted_data, key=keyfunc):
|
|
145
|
+
group_list = list(group) # Must consume before next iteration
|
|
146
|
+
process_group(key, group_list)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Avoid
|
|
152
|
+
|
|
153
|
+
- `groupby` on unsorted data (produces incorrect groups)
|
|
154
|
+
- Converting large iterators to lists unnecessarily
|
|
155
|
+
- `zip_longest` without considering `fillvalue` semantics
|
|
156
|
+
- Manual index management when `islice`, `enumerate`, `pairwise` exist
|
|
157
|
+
- Nested `product` when single `product` with multiple iterables works
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Python 3.12+ Additions
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
itertools.batched(iterable, n) # Built-in batching
|
|
165
|
+
itertools.chunked(iterable, n) # Alias for batched
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Validation Considerations
|
|
171
|
+
|
|
172
|
+
- All return iterators (lazy)
|
|
173
|
+
- Type checkers understand generic types
|
|
174
|
+
- `groupby` groups are iterators — consume immediately
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Related Skills
|
|
179
|
+
|
|
180
|
+
- `core/data_structures.md`
|
|
181
|
+
- `core/advanced_python.md` (generators)
|
|
182
|
+
- `stdlib/collections.md`
|
|
183
|
+
- `stdlib/functools.md`
|