@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 +1 -0
- package/effective-python-skill/SKILL.md +199 -0
- package/effective-python-skill/ref-01-pythonic-thinking.md +202 -0
- package/effective-python-skill/ref-02-lists-and-dicts.md +146 -0
- package/effective-python-skill/ref-03-functions.md +186 -0
- package/effective-python-skill/ref-04-comprehensions-generators.md +211 -0
- package/effective-python-skill/ref-05-classes-interfaces.md +188 -0
- package/effective-python-skill/ref-06-metaclasses-attributes.md +209 -0
- package/effective-python-skill/ref-07-concurrency.md +213 -0
- package/effective-python-skill/ref-08-robustness-performance.md +248 -0
- package/effective-python-skill/ref-09-testing-debugging.md +253 -0
- package/effective-python-skill/ref-10-collaboration.md +175 -0
- package/package.json +8 -2
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
|