@booklib/skills 1.0.0 → 1.2.0

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.
package/README.md CHANGED
@@ -89,6 +89,7 @@ cp -r skills/effective-kotlin /path/to/project/.claude/skills/
89
89
  | [data-pipelines](./data-pipelines/) | Data pipeline practices from James Densmore's *Data Pipelines Pocket Reference* — ingestion, streaming, transformation, and orchestration |
90
90
  | [design-patterns](./design-patterns/) | Apply and review GoF design patterns from *Head First Design Patterns* — creational, structural, and behavioral patterns |
91
91
  | [domain-driven-design](./domain-driven-design/) | Design and review software using patterns from Eric Evans' *Domain-Driven Design* — tactical and strategic patterns, and Ubiquitous Language |
92
+ | [effective-python](./effective-python-skill/) | Python best practices from Brett Slatkin's *Effective Python* (2nd Edition) — Pythonic thinking, functions, classes, concurrency, and testing |
92
93
  | [effective-java](./effective-java/) | Java best practices from Joshua Bloch's *Effective Java* (3rd Edition) — object creation, generics, enums, lambdas, and concurrency |
93
94
  | [effective-kotlin](./effective-kotlin/) | Best practices from Marcin Moskała's *Effective Kotlin* (2nd Ed) — safety, readability, reusability, and abstraction |
94
95
  | [kotlin-in-action](./kotlin-in-action/) | Practices from *Kotlin in Action* (2nd Ed) — functions, classes, lambdas, nullability, and coroutines |
@@ -0,0 +1,199 @@
1
+ ---
2
+ name: effective-python
3
+ description: Review existing Python code and write new Python code following the 90 best practices from "Effective Python" by Brett Slatkin (2nd Edition). Use when writing Python, reviewing Python code, or wanting idiomatic, Pythonic solutions.
4
+ ---
5
+
6
+ # Effective Python Skill
7
+
8
+ Apply the 90 items from Brett Slatkin's "Effective Python" (2nd Edition) to review existing code and write new Python code. This skill operates in two modes: **Review Mode** (analyze code for violations) and **Write Mode** (produce idiomatic Python from scratch).
9
+
10
+ ## Reference Files
11
+
12
+ This skill includes categorized reference files with all 90 items:
13
+
14
+ - `ref-01-pythonic-thinking.md` — Items 1-10: PEP 8, f-strings, bytes/str, walrus operator, unpacking, enumerate, zip, slicing
15
+ - `ref-02-lists-and-dicts.md` — Items 11-18: Slicing, sorting, dict ordering, defaultdict, __missing__
16
+ - `ref-03-functions.md` — Items 19-26: Exceptions vs None, closures, *args/**kwargs, keyword-only args, decorators
17
+ - `ref-04-comprehensions-generators.md` — Items 27-36: Comprehensions, generators, yield from, itertools
18
+ - `ref-05-classes-interfaces.md` — Items 37-43: Composition, @classmethod, super(), mix-ins, public attrs
19
+ - `ref-06-metaclasses-attributes.md` — Items 44-51: @property, descriptors, __getattr__, __init_subclass__, class decorators
20
+ - `ref-07-concurrency.md` — Items 52-64: subprocess, threads, Lock, Queue, coroutines, asyncio
21
+ - `ref-08-robustness-performance.md` — Items 65-76: try/except, contextlib, datetime, decimal, profiling, data structures
22
+ - `ref-09-testing-debugging.md` — Items 77-85: TestCase, mocks, dependency injection, pdb, tracemalloc
23
+ - `ref-10-collaboration.md` — Items 86-90: Docstrings, packages, root exceptions, virtual environments
24
+
25
+ ## How to Use This Skill
26
+
27
+ **Before responding**, read the relevant reference files based on the code's topic. For a general review, read all files. For targeted work (e.g., writing async code), read the specific reference (e.g., `ref-07-concurrency.md`).
28
+
29
+ ---
30
+
31
+ ## Mode 1: Code Review
32
+
33
+ When the user asks you to **review** existing Python code, follow this process:
34
+
35
+ ### Step 1: Read Relevant References
36
+ Determine which chapters apply to the code under review and read those reference files. If unsure, read all of them.
37
+
38
+ ### Step 2: Analyze the Code
39
+ For each relevant item from the book, check whether the code follows or violates the guideline. Focus on:
40
+
41
+ 1. **Style and Idiom** (Items 1-10): Is it Pythonic? Does it use f-strings, unpacking, enumerate, zip properly?
42
+ 2. **Data Structures** (Items 11-18): Are lists and dicts used correctly? Is sorting done with key functions?
43
+ 3. **Function Design** (Items 19-26): Do functions raise exceptions instead of returning None? Are args well-structured?
44
+ 4. **Comprehensions & Generators** (Items 27-36): Are comprehensions preferred over map/filter? Are generators used for large sequences?
45
+ 5. **Class Design** (Items 37-43): Is composition preferred over deep nesting? Are mix-ins used correctly?
46
+ 6. **Metaclasses & Attributes** (Items 44-51): Are plain attributes used instead of getter/setter methods? Is @property used appropriately?
47
+ 7. **Concurrency** (Items 52-64): Are threads used only for I/O? Is asyncio structured correctly?
48
+ 8. **Robustness** (Items 65-76): Is error handling structured with try/except/else/finally? Are the right data structures chosen?
49
+ 9. **Testing** (Items 77-85): Are tests well-structured? Are mocks used appropriately?
50
+ 10. **Collaboration** (Items 86-90): Are docstrings present? Are APIs stable?
51
+
52
+ ### Step 3: Report Findings
53
+ For each issue found, report:
54
+ - **Item number and name** (e.g., "Item 4: Prefer Interpolated F-Strings")
55
+ - **Location** in the code
56
+ - **What's wrong** (the anti-pattern)
57
+ - **How to fix it** (the Pythonic way)
58
+ - **Priority**: Critical (bugs/correctness), Important (maintainability), Suggestion (style)
59
+
60
+ ### Step 4: Provide Fixed Code
61
+ Offer a corrected version of the code with all issues addressed, with comments explaining each change.
62
+
63
+ ---
64
+
65
+ ## Mode 2: Writing New Code
66
+
67
+ When the user asks you to **write** new Python code, follow these principles:
68
+
69
+ ### Always Apply These Core Practices
70
+
71
+ 1. **Follow PEP 8** — Use consistent naming (snake_case for functions/variables, PascalCase for classes). Use `pylint` and `black`-compatible style.
72
+
73
+ 2. **Use f-strings** for string formatting (Item 4). Never use % or .format() for simple cases.
74
+
75
+ 3. **Use unpacking** instead of indexing (Item 6). Prefer `first, second = my_list` over `my_list[0]`.
76
+
77
+ 4. **Use enumerate** instead of range(len(...)) (Item 7).
78
+
79
+ 5. **Use zip** to iterate over multiple lists in parallel (Item 8). Use `zip_longest` from itertools when lengths differ.
80
+
81
+ 6. **Avoid else blocks** after for/while loops (Item 9).
82
+
83
+ 7. **Use assignment expressions** (:= walrus operator) to reduce repetition when appropriate (Item 10).
84
+
85
+ 8. **Raise exceptions** instead of returning None for failure cases (Item 20).
86
+
87
+ 9. **Use keyword-only arguments** for clarity (Item 25). Use positional-only args to separate API from implementation (Item 25).
88
+
89
+ 10. **Use functools.wraps** on all decorators (Item 26).
90
+
91
+ 11. **Prefer comprehensions** over map/filter (Item 27). Keep them simple — no more than two expressions (Item 28).
92
+
93
+ 12. **Use generators** for large sequences instead of returning lists (Item 30).
94
+
95
+ 13. **Prefer composition** over deeply nested classes (Item 37).
96
+
97
+ 14. **Use @classmethod** for polymorphic constructors (Item 39).
98
+
99
+ 15. **Always call super().__init__** (Item 40).
100
+
101
+ 16. **Use plain attributes** instead of getter/setter methods. Use @property for special behavior (Item 44).
102
+
103
+ 17. **Use try/except/else/finally** structure correctly (Item 65).
104
+
105
+ 18. **Write docstrings** for every module, class, and function (Item 84).
106
+
107
+ ### Code Structure Template
108
+
109
+ When writing new modules or classes, follow this structure:
110
+
111
+ ```python
112
+ """Module docstring describing purpose."""
113
+
114
+ # Standard library imports
115
+ # Third-party imports
116
+ # Local imports
117
+
118
+ # Module-level constants
119
+
120
+ class MyClass:
121
+ """Class docstring describing purpose and usage.
122
+
123
+ Attributes:
124
+ attr_name: Description of attribute.
125
+ """
126
+
127
+ def __init__(self, param: type) -> None:
128
+ """Initialize with description of params."""
129
+ self.param = param # Use public attributes (Item 42)
130
+
131
+ @classmethod
132
+ def from_alternative(cls, data):
133
+ """Alternative constructor (Item 39)."""
134
+ return cls(processed_data)
135
+
136
+ def method(self, arg: type) -> return_type:
137
+ """Method docstring.
138
+
139
+ Args:
140
+ arg: Description.
141
+
142
+ Returns:
143
+ Description of return value.
144
+
145
+ Raises:
146
+ ValueError: When arg is invalid (Item 20).
147
+ """
148
+ pass
149
+ ```
150
+
151
+ ### Concurrency Guidelines
152
+
153
+ - Use `subprocess` for managing child processes (Item 52)
154
+ - Use threads **only** for blocking I/O, never for parallelism (Item 53)
155
+ - Use `threading.Lock` to prevent data races (Item 54)
156
+ - Use `Queue` for coordinating work between threads (Item 55)
157
+ - Use `asyncio` for highly concurrent I/O (Item 60)
158
+ - Never mix blocking calls in async code (Item 62)
159
+
160
+ ### Testing Guidelines
161
+
162
+ - Subclass `TestCase` and use `setUp`/`tearDown` (Item 78)
163
+ - Use `unittest.mock` for complex dependencies (Item 78)
164
+ - Encapsulate dependencies to make code testable (Item 79)
165
+ - Use `pdb.set_trace()` or `breakpoint()` for debugging (Item 80)
166
+ - Use `tracemalloc` for memory debugging (Item 81)
167
+
168
+ ---
169
+
170
+ ## Priority of Items by Impact
171
+
172
+ When time is limited, focus on these highest-impact items first:
173
+
174
+ ### Critical (Correctness & Bugs)
175
+ - Item 20: Raise exceptions instead of returning None
176
+ - Item 53: Use threads for I/O only, not parallelism
177
+ - Item 54: Use Lock to prevent data races
178
+ - Item 40: Initialize parent classes with super()
179
+ - Item 65: Use try/except/else/finally correctly
180
+ - Item 73: Use datetime instead of time module for timezone handling
181
+
182
+ ### Important (Maintainability)
183
+ - Item 1: Follow PEP 8 style
184
+ - Item 4: Use f-strings
185
+ - Item 19: Never unpack more than 3 variables
186
+ - Item 25: Use keyword-only and positional-only arguments
187
+ - Item 26: Use functools.wraps for decorators
188
+ - Item 37: Compose classes instead of deep nesting
189
+ - Item 42: Prefer public attributes over private
190
+ - Item 44: Use plain attributes over getter/setter
191
+ - Item 84: Write docstrings for all public APIs
192
+
193
+ ### Suggestions (Polish & Optimization)
194
+ - Item 7: Use enumerate instead of range
195
+ - Item 8: Use zip for parallel iteration
196
+ - Item 10: Use walrus operator to reduce repetition
197
+ - Item 27: Use comprehensions over map/filter
198
+ - Item 30: Use generators for large sequences
199
+ - Item 70: Profile before optimizing (cProfile)
@@ -0,0 +1,202 @@
1
+ # Chapter 1: Pythonic Thinking (Items 1-10)
2
+
3
+ ## Item 1: Know Which Version of Python You're Using
4
+ - Use `python3` explicitly, not `python`
5
+ - Check version with `python3 --version` or `sys.version_info`
6
+ - Python 2 is end-of-life; always target Python 3
7
+
8
+ ## Item 2: Follow the PEP 8 Style Guide
9
+ **Whitespace:**
10
+ - Use 4 spaces for indentation (never tabs)
11
+ - Lines should be 79 characters or fewer
12
+ - Continuations should be indented by 4 extra spaces
13
+ - Put two blank lines before/after top-level functions and classes
14
+ - One blank line between methods in a class
15
+
16
+ **Naming:**
17
+ - Functions, variables, attributes: `lowercase_underscore`
18
+ - Protected instance attributes: `_leading_underscore`
19
+ - Private instance attributes: `__double_leading_underscore`
20
+ - Classes and exceptions: `CapitalizedWord`
21
+ - Module-level constants: `ALL_CAPS`
22
+ - Instance methods use `self` as first parameter; class methods use `cls`
23
+
24
+ **Expressions & Statements:**
25
+ - Use inline negation (`if a is not b`) instead of negating positive (`if not a is b`)
26
+ - Don't check for empty containers with length (`if len(list) == 0`); use `if not list`
27
+ - Use `if list` to check for non-empty
28
+ - Avoid single-line `if`, `for`, `while`, `except`
29
+ - Always use absolute imports, not relative
30
+ - Put imports at top in order: stdlib, third-party, local
31
+
32
+ **Tools:** Use `pylint` for static analysis, `black` for formatting.
33
+
34
+ ## Item 3: Know the Differences Between bytes and str
35
+ - `bytes` contains raw unsigned 8-bit values; `str` contains Unicode code points
36
+ - Use helper functions to convert between them:
37
+
38
+ ```python
39
+ # BAD
40
+ def to_str(data):
41
+ if isinstance(data, bytes):
42
+ return data.decode('utf-8')
43
+ return data
44
+
45
+ # GOOD — be explicit about encoding
46
+ def to_str(bytes_or_str):
47
+ if isinstance(bytes_or_str, bytes):
48
+ value = bytes_or_str.decode('utf-8')
49
+ else:
50
+ value = bytes_or_str
51
+ return value
52
+
53
+ def to_bytes(bytes_or_str):
54
+ if isinstance(bytes_or_str, str):
55
+ value = bytes_or_str.encode('utf-8')
56
+ else:
57
+ value = bytes_or_str
58
+ return value
59
+ ```
60
+
61
+ - Use `'rb'` and `'wb'` modes for binary file I/O
62
+ - Specify encoding explicitly: `open(path, 'r', encoding='utf-8')`
63
+
64
+ ## Item 4: Prefer Interpolated F-Strings Over C-style Format Strings and str.format
65
+ ```python
66
+ # BAD — C-style
67
+ 'Hello, %s. You are %d.' % (name, age)
68
+
69
+ # BAD — str.format
70
+ 'Hello, {}. You are {}.'.format(name, age)
71
+
72
+ # GOOD — f-string
73
+ f'Hello, {name}. You are {age}.'
74
+
75
+ # F-strings support expressions
76
+ f'{key!r}: {value:.2f}'
77
+ f'result: {some_func(x)}'
78
+
79
+ # Multi-line f-strings
80
+ f'{key:<10} = {value:.2f}'
81
+ ```
82
+
83
+ ## Item 5: Write Helper Functions Instead of Complex Expressions
84
+ - If an expression is hard to read, move it to a helper function
85
+ - Clarity over brevity: `if`/`else` is clearer than `or` for defaults
86
+
87
+ ```python
88
+ # BAD
89
+ values = query_string.get('red', [''])
90
+ red = int(values[0]) if values[0] else 0
91
+
92
+ # GOOD
93
+ def get_first_int(values, key, default=0):
94
+ found = values.get(key, [''])
95
+ if found[0]:
96
+ return int(found[0])
97
+ return default
98
+
99
+ red = get_first_int(values, 'red')
100
+ ```
101
+
102
+ ## Item 6: Prefer Multiple Assignment Unpacking Over Indexing
103
+ ```python
104
+ # BAD
105
+ item = ('Peanut Butter', 3.50)
106
+ name = item[0]
107
+ price = item[1]
108
+
109
+ # GOOD
110
+ name, price = item
111
+
112
+ # Works with nested structures
113
+ ((name1, cal1), (name2, cal2)) = snacks
114
+
115
+ # Use _ for unused values
116
+ _, price = item
117
+
118
+ # Swap without temp variable
119
+ a, b = b, a
120
+ ```
121
+
122
+ ## Item 7: Prefer enumerate Over range
123
+ ```python
124
+ # BAD
125
+ for i in range(len(flavor_list)):
126
+ flavor = flavor_list[i]
127
+ print(f'{i + 1}: {flavor}')
128
+
129
+ # GOOD
130
+ for i, flavor in enumerate(flavor_list):
131
+ print(f'{i + 1}: {flavor}')
132
+
133
+ # Start from a different index
134
+ for i, flavor in enumerate(flavor_list, 1):
135
+ print(f'{i}: {flavor}')
136
+ ```
137
+
138
+ ## Item 8: Use zip to Process Iterators in Parallel
139
+ ```python
140
+ # BAD
141
+ for i in range(len(names)):
142
+ print(f'{names[i]}: {counts[i]}')
143
+
144
+ # GOOD
145
+ for name, count in zip(names, counts):
146
+ print(f'{name}: {count}')
147
+
148
+ # When lengths differ, use zip_longest
149
+ from itertools import zip_longest
150
+ for name, count in zip_longest(names, counts, fillvalue=0):
151
+ print(f'{name}: {count}')
152
+ ```
153
+
154
+ - `zip` truncates to shortest iterator (use `itertools.zip_longest` if needed)
155
+ - zip is lazy — produces one tuple at a time
156
+
157
+ ## Item 9: Avoid else Blocks After for and while Loops
158
+ - `else` on loops runs when the loop completes *without* `break`
159
+ - This is counterintuitive and confuses readers
160
+ - Instead, use a helper function with early return:
161
+
162
+ ```python
163
+ # BAD — confusing else on loop
164
+ for i in range(n):
165
+ if condition(i):
166
+ break
167
+ else:
168
+ handle_no_break()
169
+
170
+ # GOOD — helper function
171
+ def find_match(n):
172
+ for i in range(n):
173
+ if condition(i):
174
+ return i
175
+ return None # explicit "not found"
176
+
177
+ result = find_match(n)
178
+ if result is None:
179
+ handle_no_match()
180
+ ```
181
+
182
+ ## Item 10: Prevent Repetition with Assignment Expressions (Walrus Operator)
183
+ ```python
184
+ # BAD — repeated call or extra variable
185
+ count = fresh_fruit.get('lemon', 0)
186
+ if count:
187
+ make_lemonade(count)
188
+
189
+ # GOOD — walrus operator
190
+ if count := fresh_fruit.get('lemon', 0):
191
+ make_lemonade(count)
192
+
193
+ # Useful in while loops
194
+ while chunk := f.read(8192):
195
+ process(chunk)
196
+
197
+ # Useful in comprehensions
198
+ result = [y for x in data if (y := f(x)) is not None]
199
+ ```
200
+
201
+ - Use `:=` when you need to both assign and test a value
202
+ - Don't overuse — only when it clearly reduces repetition
@@ -0,0 +1,146 @@
1
+ # Chapter 2: Lists and Dictionaries (Items 11-18)
2
+
3
+ ## Item 11: Know How to Slice Sequences
4
+ ```python
5
+ a = [1, 2, 3, 4, 5, 6, 7, 8]
6
+
7
+ # Basic slicing
8
+ a[:4] # [1, 2, 3, 4] — first 4
9
+ a[-3:] # [6, 7, 8] — last 3
10
+ a[3:5] # [4, 5]
11
+
12
+ # Don't use 0 for start or len for end
13
+ a[:5] # GOOD
14
+ a[0:5] # BAD — redundant 0
15
+
16
+ # Slicing makes a new list (shallow copy)
17
+ b = a[:] # copy of a
18
+
19
+ # Slice assignment replaces in place
20
+ a[2:4] = [10, 11] # can be different length
21
+ ```
22
+
23
+ ## Item 12: Avoid Striding and Slicing in a Single Expression
24
+ ```python
25
+ # BAD — confusing stride + slice
26
+ x = a[2::2] # skip start, stride by 2
27
+ x = a[-2::-2] # reverse with stride
28
+
29
+ # GOOD — separate steps
30
+ y = a[::2] # stride first
31
+ z = y[1:3] # then slice
32
+
33
+ # Reverse a sequence
34
+ x = a[::-1] # OK for simple reversal, but avoid combining with slicing
35
+ ```
36
+
37
+ ## Item 13: Prefer Catch-All Unpacking Over Slicing
38
+ ```python
39
+ # BAD — manual slicing
40
+ oldest = ages[0]
41
+ rest = ages[1:]
42
+
43
+ # GOOD — starred expression
44
+ oldest, *rest = ages
45
+ oldest, second, *rest = ages
46
+ first, *middle, last = ages
47
+
48
+ # Works with any iterable
49
+ it = iter(range(10))
50
+ first, second, *rest = it
51
+ ```
52
+
53
+ - Starred expressions always produce a list (may be empty)
54
+ - Cannot have more than one starred expression in a single assignment
55
+
56
+ ## Item 14: Sort by Complex Criteria Using the key Parameter
57
+ ```python
58
+ # Sort with key function
59
+ tools = [Tool('drill', 4), Tool('saw', 2)]
60
+ tools.sort(key=lambda x: x.weight)
61
+
62
+ # Multiple criteria — use tuple
63
+ tools.sort(key=lambda x: (x.name, x.weight))
64
+
65
+ # Reverse one criterion using negation (numeric)
66
+ tools.sort(key=lambda x: (-x.weight, x.name))
67
+
68
+ # For non-numeric reverse, use multiple sort passes (stable sort)
69
+ tools.sort(key=lambda x: x.name) # secondary first
70
+ tools.sort(key=lambda x: x.weight, reverse=True) # primary last
71
+ ```
72
+
73
+ - Python sort is stable — equal elements maintain relative order
74
+ - Use `operator.attrgetter` for attribute access as key
75
+
76
+ ## Item 15: Be Cautious When Relying on dict Insertion Order
77
+ - Since Python 3.7, dicts maintain insertion order
78
+ - But don't assume all dict-like objects do (e.g., custom classes)
79
+ - Use explicit ordering when you need it:
80
+
81
+ ```python
82
+ # If order matters and you're creating a protocol
83
+ class MyDB:
84
+ def __init__(self):
85
+ self._data = {}
86
+
87
+ # Be explicit that order is part of the contract
88
+ ```
89
+
90
+ - For **kwargs, insertion order is preserved
91
+ - Standard dict methods (keys, values, items) follow insertion order
92
+
93
+ ## Item 16: Prefer get Over in and KeyError to Handle Missing Dictionary Keys
94
+ ```python
95
+ # BAD — check then access
96
+ if key in counters:
97
+ count = counters[key]
98
+ else:
99
+ count = 0
100
+ counters[key] = count + 1
101
+
102
+ # BAD — try/except
103
+ try:
104
+ count = counters[key]
105
+ except KeyError:
106
+ count = 0
107
+ counters[key] = count + 1
108
+
109
+ # GOOD — use get
110
+ count = counters.get(key, 0)
111
+ counters[key] = count + 1
112
+
113
+ # For complex default values, consider setdefault or defaultdict
114
+ ```
115
+
116
+ ## Item 17: Prefer defaultdict Over setdefault to Handle Missing Items in Internal State
117
+ ```python
118
+ from collections import defaultdict
119
+
120
+ # BAD — setdefault (confusing API)
121
+ visits = {}
122
+ visits.setdefault('France', []).append('Paris')
123
+
124
+ # GOOD — defaultdict
125
+ visits = defaultdict(list)
126
+ visits['France'].append('Paris')
127
+ ```
128
+
129
+ - `defaultdict` is clearer when you control the dict creation
130
+ - `setdefault` is better when you don't control the dict (external data)
131
+
132
+ ## Item 18: Know How to Construct Key-Dependent Default Values with __missing__
133
+ ```python
134
+ # When the default value depends on the key, use __missing__
135
+ class Pictures(dict):
136
+ def __missing__(self, key):
137
+ value = open_picture(key) # default depends on key
138
+ self[key] = value
139
+ return value
140
+
141
+ pictures = Pictures()
142
+ handle = pictures[path] # calls __missing__ if path not present
143
+ ```
144
+
145
+ - Use when `defaultdict` isn't sufficient (default factory doesn't receive the key)
146
+ - `__missing__` is called by `__getitem__` when key is not found