@pilllesss/yorn 1.0.182 → 1.0.183
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.
Potentially problematic release.
This version of @pilllesss/yorn might be problematic. Click here for more details.
- package/README.md +1 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/skills/code-review/LICENSE +21 -0
- package/dist/skills/code-review/SKILL.md +233 -0
- package/dist/skills/code-review/assets/pr-review-template.md +137 -0
- package/dist/skills/code-review/assets/review-checklist.md +123 -0
- package/dist/skills/code-review/reference/angular.md +768 -0
- package/dist/skills/code-review/reference/architecture-review-guide.md +472 -0
- package/dist/skills/code-review/reference/c.md +890 -0
- package/dist/skills/code-review/reference/code-quality-universal.md +488 -0
- package/dist/skills/code-review/reference/code-review-best-practices.md +136 -0
- package/dist/skills/code-review/reference/common-bugs-checklist.md +302 -0
- package/dist/skills/code-review/reference/cpp.md +893 -0
- package/dist/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
- package/dist/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
- package/dist/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
- package/dist/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
- package/dist/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
- package/dist/skills/code-review/reference/csharp.md +525 -0
- package/dist/skills/code-review/reference/css-less-sass.md +661 -0
- package/dist/skills/code-review/reference/dart.md +670 -0
- package/dist/skills/code-review/reference/django.md +985 -0
- package/dist/skills/code-review/reference/fastapi.md +580 -0
- package/dist/skills/code-review/reference/go.md +993 -0
- package/dist/skills/code-review/reference/java.md +409 -0
- package/dist/skills/code-review/reference/java8.md +586 -0
- package/dist/skills/code-review/reference/kotlin.md +1018 -0
- package/dist/skills/code-review/reference/nestjs.md +593 -0
- package/dist/skills/code-review/reference/performance-review-guide.md +816 -0
- package/dist/skills/code-review/reference/php.md +684 -0
- package/dist/skills/code-review/reference/python.md +1073 -0
- package/dist/skills/code-review/reference/qt.md +757 -0
- package/dist/skills/code-review/reference/react.md +871 -0
- package/dist/skills/code-review/reference/ruby.md +964 -0
- package/dist/skills/code-review/reference/rust.md +846 -0
- package/dist/skills/code-review/reference/security-review-guide.md +494 -0
- package/dist/skills/code-review/reference/svelte.md +1064 -0
- package/dist/skills/code-review/reference/swift.md +936 -0
- package/dist/skills/code-review/reference/typescript.md +1016 -0
- package/dist/skills/code-review/reference/vue.md +924 -0
- package/dist/skills/code-review/reference/zig.md +440 -0
- package/dist/skills/code-review/scripts/pr-analyzer.py +435 -0
- package/dist/skills/code-review/scripts/test_pr_analyzer.py +380 -0
- package/dist/yorn.cjs +628 -628
- package/package.json +2 -2
|
@@ -0,0 +1,1073 @@
|
|
|
1
|
+
# Python Code Review Guide
|
|
2
|
+
|
|
3
|
+
> Python 代码审查指南,覆盖类型注解、async/await、测试、异常处理、性能优化等核心主题。
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
- [类型注解](#类型注解)
|
|
8
|
+
- [异步编程](#异步编程)
|
|
9
|
+
- [异常处理](#异常处理)
|
|
10
|
+
- [常见陷阱](#常见陷阱)
|
|
11
|
+
- [测试最佳实践](#测试最佳实践)
|
|
12
|
+
- [性能优化](#性能优化)
|
|
13
|
+
- [代码风格](#代码风格)
|
|
14
|
+
- [Review Checklist](#review-checklist)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 类型注解
|
|
19
|
+
|
|
20
|
+
### 基础类型注解
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
# ❌ 没有类型注解,IDE 无法提供帮助
|
|
24
|
+
def process_data(data, count):
|
|
25
|
+
return data[:count]
|
|
26
|
+
|
|
27
|
+
# ✅ 使用类型注解
|
|
28
|
+
def process_data(data: str, count: int) -> str:
|
|
29
|
+
return data[:count]
|
|
30
|
+
|
|
31
|
+
# ✅ 复杂类型使用 typing 模块
|
|
32
|
+
from typing import Optional, Union
|
|
33
|
+
|
|
34
|
+
def find_user(user_id: int) -> Optional[User]:
|
|
35
|
+
"""返回用户或 None"""
|
|
36
|
+
return db.get(user_id)
|
|
37
|
+
|
|
38
|
+
def handle_input(value: Union[str, int]) -> str:
|
|
39
|
+
"""接受字符串或整数"""
|
|
40
|
+
return str(value)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 容器类型注解
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from typing import List, Dict, Set, Tuple, Sequence
|
|
47
|
+
|
|
48
|
+
# ❌ 不精确的类型
|
|
49
|
+
def get_names(users: list) -> list:
|
|
50
|
+
return [u.name for u in users]
|
|
51
|
+
|
|
52
|
+
# ✅ 精确的容器类型(Python 3.9+ 可直接用 list[User])
|
|
53
|
+
def get_names(users: List[User]) -> List[str]:
|
|
54
|
+
return [u.name for u in users]
|
|
55
|
+
|
|
56
|
+
# ✅ 只读序列用 Sequence(更灵活)
|
|
57
|
+
def process_items(items: Sequence[str]) -> int:
|
|
58
|
+
return len(items)
|
|
59
|
+
|
|
60
|
+
# ✅ 字典类型
|
|
61
|
+
def count_words(text: str) -> Dict[str, int]:
|
|
62
|
+
words: Dict[str, int] = {}
|
|
63
|
+
for word in text.split():
|
|
64
|
+
words[word] = words.get(word, 0) + 1
|
|
65
|
+
return words
|
|
66
|
+
|
|
67
|
+
# ✅ 元组(固定长度和类型)
|
|
68
|
+
def get_point() -> Tuple[float, float]:
|
|
69
|
+
return (1.0, 2.0)
|
|
70
|
+
|
|
71
|
+
# ✅ 可变长度元组
|
|
72
|
+
def get_scores() -> Tuple[int, ...]:
|
|
73
|
+
return (90, 85, 92, 88)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 泛型与 TypeVar
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from typing import TypeVar, Generic, List, Callable
|
|
80
|
+
|
|
81
|
+
T = TypeVar('T')
|
|
82
|
+
K = TypeVar('K')
|
|
83
|
+
V = TypeVar('V')
|
|
84
|
+
|
|
85
|
+
# ✅ 泛型函数
|
|
86
|
+
def first(items: List[T]) -> T | None:
|
|
87
|
+
return items[0] if items else None
|
|
88
|
+
|
|
89
|
+
# ✅ 有约束的 TypeVar
|
|
90
|
+
from typing import Hashable
|
|
91
|
+
H = TypeVar('H', bound=Hashable)
|
|
92
|
+
|
|
93
|
+
def dedupe(items: List[H]) -> List[H]:
|
|
94
|
+
return list(set(items))
|
|
95
|
+
|
|
96
|
+
# ✅ 泛型类
|
|
97
|
+
class Cache(Generic[K, V]):
|
|
98
|
+
def __init__(self) -> None:
|
|
99
|
+
self._data: Dict[K, V] = {}
|
|
100
|
+
|
|
101
|
+
def get(self, key: K) -> V | None:
|
|
102
|
+
return self._data.get(key)
|
|
103
|
+
|
|
104
|
+
def set(self, key: K, value: V) -> None:
|
|
105
|
+
self._data[key] = value
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Callable 与回调函数
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from typing import Callable, Awaitable
|
|
112
|
+
|
|
113
|
+
# ✅ 函数类型注解
|
|
114
|
+
Handler = Callable[[str, int], bool]
|
|
115
|
+
|
|
116
|
+
def register_handler(name: str, handler: Handler) -> None:
|
|
117
|
+
handlers[name] = handler
|
|
118
|
+
|
|
119
|
+
# ✅ 异步回调
|
|
120
|
+
AsyncHandler = Callable[[str], Awaitable[dict]]
|
|
121
|
+
|
|
122
|
+
async def fetch_with_handler(
|
|
123
|
+
url: str,
|
|
124
|
+
handler: AsyncHandler
|
|
125
|
+
) -> dict:
|
|
126
|
+
return await handler(url)
|
|
127
|
+
|
|
128
|
+
# ✅ 返回函数的函数
|
|
129
|
+
def create_multiplier(factor: int) -> Callable[[int], int]:
|
|
130
|
+
def multiplier(x: int) -> int:
|
|
131
|
+
return x * factor
|
|
132
|
+
return multiplier
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### TypedDict 与结构化数据
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from typing import TypedDict, Required, NotRequired
|
|
139
|
+
|
|
140
|
+
# ✅ 定义字典结构
|
|
141
|
+
class UserDict(TypedDict):
|
|
142
|
+
id: int
|
|
143
|
+
name: str
|
|
144
|
+
email: str
|
|
145
|
+
age: NotRequired[int] # Python 3.11+
|
|
146
|
+
|
|
147
|
+
def create_user(data: UserDict) -> User:
|
|
148
|
+
return User(**data)
|
|
149
|
+
|
|
150
|
+
# ✅ 部分必需字段
|
|
151
|
+
class ConfigDict(TypedDict, total=False):
|
|
152
|
+
debug: bool
|
|
153
|
+
timeout: int
|
|
154
|
+
host: Required[str] # 这个必须有
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Protocol 与结构化子类型
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from typing import Protocol, runtime_checkable
|
|
161
|
+
|
|
162
|
+
# ✅ 定义协议(鸭子类型的类型检查)
|
|
163
|
+
class Readable(Protocol):
|
|
164
|
+
def read(self, size: int = -1) -> bytes: ...
|
|
165
|
+
|
|
166
|
+
class Closeable(Protocol):
|
|
167
|
+
def close(self) -> None: ...
|
|
168
|
+
|
|
169
|
+
# 组合协议
|
|
170
|
+
class ReadableCloseable(Readable, Closeable, Protocol):
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
def process_stream(stream: Readable) -> bytes:
|
|
174
|
+
return stream.read()
|
|
175
|
+
|
|
176
|
+
# ✅ 运行时可检查的协议
|
|
177
|
+
@runtime_checkable
|
|
178
|
+
class Drawable(Protocol):
|
|
179
|
+
def draw(self) -> None: ...
|
|
180
|
+
|
|
181
|
+
def render(obj: object) -> None:
|
|
182
|
+
if isinstance(obj, Drawable): # 运行时检查
|
|
183
|
+
obj.draw()
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 异步编程
|
|
189
|
+
|
|
190
|
+
> 📖 通用并发模式和跨语言示例详见 [异步与并发跨语言指南](cross-cutting/async-concurrency-patterns.md)
|
|
191
|
+
|
|
192
|
+
### async/await 基础
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
import asyncio
|
|
196
|
+
|
|
197
|
+
# ❌ 同步阻塞调用
|
|
198
|
+
def fetch_all_sync(urls: list[str]) -> list[str]:
|
|
199
|
+
results = []
|
|
200
|
+
for url in urls:
|
|
201
|
+
results.append(requests.get(url).text) # 串行执行
|
|
202
|
+
return results
|
|
203
|
+
|
|
204
|
+
# ✅ 异步并发调用
|
|
205
|
+
async def fetch_url(url: str) -> str:
|
|
206
|
+
async with aiohttp.ClientSession() as session:
|
|
207
|
+
async with session.get(url) as response:
|
|
208
|
+
return await response.text()
|
|
209
|
+
|
|
210
|
+
async def fetch_all(urls: list[str]) -> list[str]:
|
|
211
|
+
tasks = [fetch_url(url) for url in urls]
|
|
212
|
+
return await asyncio.gather(*tasks) # 并发执行
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 异步上下文管理器
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from contextlib import asynccontextmanager
|
|
219
|
+
from typing import AsyncIterator
|
|
220
|
+
|
|
221
|
+
# ✅ 异步上下文管理器类
|
|
222
|
+
class AsyncDatabase:
|
|
223
|
+
async def __aenter__(self) -> 'AsyncDatabase':
|
|
224
|
+
await self.connect()
|
|
225
|
+
return self
|
|
226
|
+
|
|
227
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
228
|
+
await self.disconnect()
|
|
229
|
+
|
|
230
|
+
# ✅ 使用装饰器
|
|
231
|
+
@asynccontextmanager
|
|
232
|
+
async def get_connection() -> AsyncIterator[Connection]:
|
|
233
|
+
conn = await create_connection()
|
|
234
|
+
try:
|
|
235
|
+
yield conn
|
|
236
|
+
finally:
|
|
237
|
+
await conn.close()
|
|
238
|
+
|
|
239
|
+
async def query_data():
|
|
240
|
+
async with get_connection() as conn:
|
|
241
|
+
return await conn.fetch("SELECT * FROM users")
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 异步迭代器
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from typing import AsyncIterator
|
|
248
|
+
|
|
249
|
+
# ✅ 异步生成器
|
|
250
|
+
async def fetch_pages(url: str) -> AsyncIterator[dict]:
|
|
251
|
+
page = 1
|
|
252
|
+
while True:
|
|
253
|
+
data = await fetch_page(url, page)
|
|
254
|
+
if not data['items']:
|
|
255
|
+
break
|
|
256
|
+
yield data
|
|
257
|
+
page += 1
|
|
258
|
+
|
|
259
|
+
# ✅ 使用异步迭代
|
|
260
|
+
async def process_all_pages():
|
|
261
|
+
async for page in fetch_pages("https://api.example.com"):
|
|
262
|
+
await process_page(page)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### 任务管理与取消
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
import asyncio
|
|
269
|
+
|
|
270
|
+
# ❌ 忘记处理取消
|
|
271
|
+
async def bad_worker():
|
|
272
|
+
while True:
|
|
273
|
+
await do_work() # 无法正常取消
|
|
274
|
+
|
|
275
|
+
# ✅ 正确处理取消
|
|
276
|
+
async def good_worker():
|
|
277
|
+
try:
|
|
278
|
+
while True:
|
|
279
|
+
await do_work()
|
|
280
|
+
except asyncio.CancelledError:
|
|
281
|
+
await cleanup() # 清理资源
|
|
282
|
+
raise # 重新抛出,让调用者知道已取消
|
|
283
|
+
|
|
284
|
+
# ✅ 超时控制
|
|
285
|
+
async def fetch_with_timeout(url: str) -> str:
|
|
286
|
+
try:
|
|
287
|
+
async with asyncio.timeout(10): # Python 3.11+
|
|
288
|
+
return await fetch_url(url)
|
|
289
|
+
except asyncio.TimeoutError:
|
|
290
|
+
return ""
|
|
291
|
+
|
|
292
|
+
# ✅ 任务组(Python 3.11+)
|
|
293
|
+
async def fetch_multiple():
|
|
294
|
+
async with asyncio.TaskGroup() as tg:
|
|
295
|
+
task1 = tg.create_task(fetch_url("url1"))
|
|
296
|
+
task2 = tg.create_task(fetch_url("url2"))
|
|
297
|
+
# 所有任务完成后自动等待,异常会传播
|
|
298
|
+
return task1.result(), task2.result()
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### 同步与异步混合
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
import asyncio
|
|
305
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
306
|
+
|
|
307
|
+
# ✅ 在异步代码中运行同步函数
|
|
308
|
+
async def run_sync_in_async():
|
|
309
|
+
loop = asyncio.get_event_loop()
|
|
310
|
+
# 使用线程池执行阻塞操作
|
|
311
|
+
result = await loop.run_in_executor(
|
|
312
|
+
None, # 默认线程池
|
|
313
|
+
blocking_io_function,
|
|
314
|
+
arg1, arg2
|
|
315
|
+
)
|
|
316
|
+
return result
|
|
317
|
+
|
|
318
|
+
# ✅ 在同步代码中运行异步函数
|
|
319
|
+
def run_async_in_sync():
|
|
320
|
+
return asyncio.run(async_function())
|
|
321
|
+
|
|
322
|
+
# ❌ 不要在异步代码中使用 time.sleep
|
|
323
|
+
async def bad_delay():
|
|
324
|
+
time.sleep(1) # 会阻塞整个事件循环!
|
|
325
|
+
|
|
326
|
+
# ✅ 使用 asyncio.sleep
|
|
327
|
+
async def good_delay():
|
|
328
|
+
await asyncio.sleep(1)
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 信号量与限流
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
import asyncio
|
|
335
|
+
|
|
336
|
+
# ✅ 使用信号量限制并发
|
|
337
|
+
async def fetch_with_limit(urls: list[str], max_concurrent: int = 10):
|
|
338
|
+
semaphore = asyncio.Semaphore(max_concurrent)
|
|
339
|
+
|
|
340
|
+
async def fetch_one(url: str) -> str:
|
|
341
|
+
async with semaphore:
|
|
342
|
+
return await fetch_url(url)
|
|
343
|
+
|
|
344
|
+
return await asyncio.gather(*[fetch_one(url) for url in urls])
|
|
345
|
+
|
|
346
|
+
# ✅ 使用 asyncio.Queue 实现生产者-消费者
|
|
347
|
+
async def producer_consumer():
|
|
348
|
+
queue: asyncio.Queue[str] = asyncio.Queue(maxsize=100)
|
|
349
|
+
|
|
350
|
+
async def producer():
|
|
351
|
+
for item in items:
|
|
352
|
+
await queue.put(item)
|
|
353
|
+
await queue.put(None) # 结束信号
|
|
354
|
+
|
|
355
|
+
async def consumer():
|
|
356
|
+
while True:
|
|
357
|
+
item = await queue.get()
|
|
358
|
+
if item is None:
|
|
359
|
+
break
|
|
360
|
+
await process(item)
|
|
361
|
+
queue.task_done()
|
|
362
|
+
|
|
363
|
+
await asyncio.gather(producer(), consumer())
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## 异常处理
|
|
369
|
+
|
|
370
|
+
> 📖 通用原则和跨语言示例详见 [错误处理跨语言指南](cross-cutting/error-handling-principles.md)
|
|
371
|
+
|
|
372
|
+
### 异常捕获最佳实践
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
# ❌ Catching too broad
|
|
376
|
+
try:
|
|
377
|
+
result = risky_operation()
|
|
378
|
+
except: # Catches everything, even KeyboardInterrupt!
|
|
379
|
+
pass
|
|
380
|
+
|
|
381
|
+
# ❌ 捕获 Exception 但不处理
|
|
382
|
+
try:
|
|
383
|
+
result = risky_operation()
|
|
384
|
+
except Exception:
|
|
385
|
+
pass # 吞掉所有异常,难以调试
|
|
386
|
+
|
|
387
|
+
# ✅ Catch specific exceptions
|
|
388
|
+
try:
|
|
389
|
+
result = risky_operation()
|
|
390
|
+
except ValueError as e:
|
|
391
|
+
logger.error(f"Invalid value: {e}")
|
|
392
|
+
raise
|
|
393
|
+
except IOError as e:
|
|
394
|
+
logger.error(f"IO error: {e}")
|
|
395
|
+
return default_value
|
|
396
|
+
|
|
397
|
+
# ✅ 多个异常类型
|
|
398
|
+
try:
|
|
399
|
+
result = parse_and_process(data)
|
|
400
|
+
except (ValueError, TypeError, KeyError) as e:
|
|
401
|
+
logger.error(f"Data error: {e}")
|
|
402
|
+
raise DataProcessingError(str(e)) from e
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### 异常链
|
|
406
|
+
|
|
407
|
+
```python
|
|
408
|
+
# ❌ 丢失原始异常信息
|
|
409
|
+
try:
|
|
410
|
+
result = external_api.call()
|
|
411
|
+
except APIError as e:
|
|
412
|
+
raise RuntimeError("API failed") # 丢失了原因
|
|
413
|
+
|
|
414
|
+
# ✅ 使用 from 保留异常链
|
|
415
|
+
try:
|
|
416
|
+
result = external_api.call()
|
|
417
|
+
except APIError as e:
|
|
418
|
+
raise RuntimeError("API failed") from e
|
|
419
|
+
|
|
420
|
+
# ✅ 显式断开异常链(少见情况)
|
|
421
|
+
try:
|
|
422
|
+
result = external_api.call()
|
|
423
|
+
except APIError:
|
|
424
|
+
raise RuntimeError("API failed") from None
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### 自定义异常
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
# ✅ 定义业务异常层次结构
|
|
431
|
+
class AppError(Exception):
|
|
432
|
+
"""应用基础异常"""
|
|
433
|
+
pass
|
|
434
|
+
|
|
435
|
+
class ValidationError(AppError):
|
|
436
|
+
"""数据验证错误"""
|
|
437
|
+
def __init__(self, field: str, message: str):
|
|
438
|
+
self.field = field
|
|
439
|
+
self.message = message
|
|
440
|
+
super().__init__(f"{field}: {message}")
|
|
441
|
+
|
|
442
|
+
class NotFoundError(AppError):
|
|
443
|
+
"""资源未找到"""
|
|
444
|
+
def __init__(self, resource: str, id: str | int):
|
|
445
|
+
self.resource = resource
|
|
446
|
+
self.id = id
|
|
447
|
+
super().__init__(f"{resource} with id {id} not found")
|
|
448
|
+
|
|
449
|
+
# 使用
|
|
450
|
+
def get_user(user_id: int) -> User:
|
|
451
|
+
user = db.get(user_id)
|
|
452
|
+
if not user:
|
|
453
|
+
raise NotFoundError("User", user_id)
|
|
454
|
+
return user
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### 上下文管理器中的异常
|
|
458
|
+
|
|
459
|
+
```python
|
|
460
|
+
from contextlib import contextmanager
|
|
461
|
+
|
|
462
|
+
# ✅ 正确处理上下文管理器中的异常
|
|
463
|
+
@contextmanager
|
|
464
|
+
def transaction():
|
|
465
|
+
conn = get_connection()
|
|
466
|
+
try:
|
|
467
|
+
yield conn
|
|
468
|
+
conn.commit()
|
|
469
|
+
except Exception:
|
|
470
|
+
conn.rollback()
|
|
471
|
+
raise
|
|
472
|
+
finally:
|
|
473
|
+
conn.close()
|
|
474
|
+
|
|
475
|
+
# ✅ 使用 ExceptionGroup(Python 3.11+)
|
|
476
|
+
def process_batch(items: list) -> None:
|
|
477
|
+
errors = []
|
|
478
|
+
for item in items:
|
|
479
|
+
try:
|
|
480
|
+
process(item)
|
|
481
|
+
except Exception as e:
|
|
482
|
+
errors.append(e)
|
|
483
|
+
|
|
484
|
+
if errors:
|
|
485
|
+
raise ExceptionGroup("Batch processing failed", errors)
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## 常见陷阱
|
|
491
|
+
|
|
492
|
+
### 可变默认参数
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
# ❌ Mutable default arguments
|
|
496
|
+
def add_item(item, items=[]): # Bug! Shared across calls
|
|
497
|
+
items.append(item)
|
|
498
|
+
return items
|
|
499
|
+
|
|
500
|
+
# 问题演示
|
|
501
|
+
add_item(1) # [1]
|
|
502
|
+
add_item(2) # [1, 2] 而不是 [2]!
|
|
503
|
+
|
|
504
|
+
# ✅ Use None as default
|
|
505
|
+
def add_item(item, items=None):
|
|
506
|
+
if items is None:
|
|
507
|
+
items = []
|
|
508
|
+
items.append(item)
|
|
509
|
+
return items
|
|
510
|
+
|
|
511
|
+
# ✅ 或使用 dataclass 的 field
|
|
512
|
+
from dataclasses import dataclass, field
|
|
513
|
+
|
|
514
|
+
@dataclass
|
|
515
|
+
class Container:
|
|
516
|
+
items: list = field(default_factory=list)
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### 可变类属性
|
|
520
|
+
|
|
521
|
+
```python
|
|
522
|
+
# ❌ Using mutable class attributes
|
|
523
|
+
class User:
|
|
524
|
+
permissions = [] # Shared across all instances!
|
|
525
|
+
|
|
526
|
+
# 问题演示
|
|
527
|
+
u1 = User()
|
|
528
|
+
u2 = User()
|
|
529
|
+
u1.permissions.append("admin")
|
|
530
|
+
print(u2.permissions) # ["admin"] - 被意外共享!
|
|
531
|
+
|
|
532
|
+
# ✅ Initialize in __init__
|
|
533
|
+
class User:
|
|
534
|
+
def __init__(self):
|
|
535
|
+
self.permissions = []
|
|
536
|
+
|
|
537
|
+
# ✅ 使用 dataclass
|
|
538
|
+
@dataclass
|
|
539
|
+
class User:
|
|
540
|
+
permissions: list = field(default_factory=list)
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### 循环中的闭包
|
|
544
|
+
|
|
545
|
+
```python
|
|
546
|
+
# ❌ 闭包捕获循环变量
|
|
547
|
+
funcs = []
|
|
548
|
+
for i in range(3):
|
|
549
|
+
funcs.append(lambda: i)
|
|
550
|
+
|
|
551
|
+
print([f() for f in funcs]) # [2, 2, 2] 而不是 [0, 1, 2]!
|
|
552
|
+
|
|
553
|
+
# ✅ 使用默认参数捕获值
|
|
554
|
+
funcs = []
|
|
555
|
+
for i in range(3):
|
|
556
|
+
funcs.append(lambda i=i: i)
|
|
557
|
+
|
|
558
|
+
print([f() for f in funcs]) # [0, 1, 2]
|
|
559
|
+
|
|
560
|
+
# ✅ 使用 functools.partial
|
|
561
|
+
from functools import partial
|
|
562
|
+
|
|
563
|
+
funcs = [partial(lambda x: x, i) for i in range(3)]
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### is vs ==
|
|
567
|
+
|
|
568
|
+
```python
|
|
569
|
+
# ❌ 用 is 比较值
|
|
570
|
+
if x is 1000: # 可能不工作!
|
|
571
|
+
pass
|
|
572
|
+
|
|
573
|
+
# Python 会缓存小整数 (-5 到 256)
|
|
574
|
+
a = 256
|
|
575
|
+
b = 256
|
|
576
|
+
a is b # True
|
|
577
|
+
|
|
578
|
+
a = 257
|
|
579
|
+
b = 257
|
|
580
|
+
a is b # False!
|
|
581
|
+
|
|
582
|
+
# ✅ 用 == 比较值
|
|
583
|
+
if x == 1000:
|
|
584
|
+
pass
|
|
585
|
+
|
|
586
|
+
# ✅ is 只用于 None 和单例
|
|
587
|
+
if x is None:
|
|
588
|
+
pass
|
|
589
|
+
|
|
590
|
+
if x is True: # 严格检查布尔值
|
|
591
|
+
pass
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### 字符串拼接性能
|
|
595
|
+
|
|
596
|
+
```python
|
|
597
|
+
# ❌ 循环中拼接字符串
|
|
598
|
+
result = ""
|
|
599
|
+
for item in large_list:
|
|
600
|
+
result += str(item) # O(n²) 复杂度
|
|
601
|
+
|
|
602
|
+
# ✅ 使用 join
|
|
603
|
+
result = "".join(str(item) for item in large_list) # O(n)
|
|
604
|
+
|
|
605
|
+
# ✅ 使用 StringIO 构建大字符串
|
|
606
|
+
from io import StringIO
|
|
607
|
+
|
|
608
|
+
buffer = StringIO()
|
|
609
|
+
for item in large_list:
|
|
610
|
+
buffer.write(str(item))
|
|
611
|
+
result = buffer.getvalue()
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## 测试最佳实践
|
|
617
|
+
|
|
618
|
+
### pytest 基础
|
|
619
|
+
|
|
620
|
+
```python
|
|
621
|
+
import pytest
|
|
622
|
+
|
|
623
|
+
# ✅ 清晰的测试命名
|
|
624
|
+
def test_user_creation_with_valid_email():
|
|
625
|
+
user = User(email="test@example.com")
|
|
626
|
+
assert user.email == "test@example.com"
|
|
627
|
+
|
|
628
|
+
def test_user_creation_with_invalid_email_raises_error():
|
|
629
|
+
with pytest.raises(ValidationError):
|
|
630
|
+
User(email="invalid")
|
|
631
|
+
|
|
632
|
+
# ✅ 使用参数化测试
|
|
633
|
+
@pytest.mark.parametrize("input,expected", [
|
|
634
|
+
("hello", "HELLO"),
|
|
635
|
+
("World", "WORLD"),
|
|
636
|
+
("", ""),
|
|
637
|
+
("123", "123"),
|
|
638
|
+
])
|
|
639
|
+
def test_uppercase(input: str, expected: str):
|
|
640
|
+
assert input.upper() == expected
|
|
641
|
+
|
|
642
|
+
# ✅ 测试异常
|
|
643
|
+
def test_division_by_zero():
|
|
644
|
+
with pytest.raises(ZeroDivisionError) as exc_info:
|
|
645
|
+
1 / 0
|
|
646
|
+
assert "division by zero" in str(exc_info.value)
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### Fixtures
|
|
650
|
+
|
|
651
|
+
```python
|
|
652
|
+
import pytest
|
|
653
|
+
from typing import Generator
|
|
654
|
+
|
|
655
|
+
# ✅ 基础 fixture
|
|
656
|
+
@pytest.fixture
|
|
657
|
+
def user() -> User:
|
|
658
|
+
return User(name="Test User", email="test@example.com")
|
|
659
|
+
|
|
660
|
+
def test_user_name(user: User):
|
|
661
|
+
assert user.name == "Test User"
|
|
662
|
+
|
|
663
|
+
# ✅ 带清理的 fixture
|
|
664
|
+
@pytest.fixture
|
|
665
|
+
def database() -> Generator[Database, None, None]:
|
|
666
|
+
db = Database()
|
|
667
|
+
db.connect()
|
|
668
|
+
yield db
|
|
669
|
+
db.disconnect() # 测试后清理
|
|
670
|
+
|
|
671
|
+
# ✅ 异步 fixture
|
|
672
|
+
@pytest.fixture
|
|
673
|
+
async def async_client() -> AsyncGenerator[AsyncClient, None]:
|
|
674
|
+
async with AsyncClient() as client:
|
|
675
|
+
yield client
|
|
676
|
+
|
|
677
|
+
# ✅ 共享 fixture(conftest.py)
|
|
678
|
+
# conftest.py
|
|
679
|
+
@pytest.fixture(scope="session")
|
|
680
|
+
def app():
|
|
681
|
+
"""整个测试会话共享的 app 实例"""
|
|
682
|
+
return create_app()
|
|
683
|
+
|
|
684
|
+
@pytest.fixture(scope="module")
|
|
685
|
+
def db(app):
|
|
686
|
+
"""每个测试模块共享的数据库连接"""
|
|
687
|
+
return app.db
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### Mock 与 Patch
|
|
691
|
+
|
|
692
|
+
```python
|
|
693
|
+
from unittest.mock import Mock, patch, AsyncMock
|
|
694
|
+
|
|
695
|
+
# ✅ Mock 外部依赖
|
|
696
|
+
def test_send_email():
|
|
697
|
+
mock_client = Mock()
|
|
698
|
+
mock_client.send.return_value = True
|
|
699
|
+
|
|
700
|
+
service = EmailService(client=mock_client)
|
|
701
|
+
result = service.send_welcome_email("user@example.com")
|
|
702
|
+
|
|
703
|
+
assert result is True
|
|
704
|
+
mock_client.send.assert_called_once_with(
|
|
705
|
+
to="user@example.com",
|
|
706
|
+
subject="Welcome!",
|
|
707
|
+
body=ANY,
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
# ✅ Patch 模块级函数
|
|
711
|
+
@patch("myapp.services.external_api.call")
|
|
712
|
+
def test_with_patched_api(mock_call):
|
|
713
|
+
mock_call.return_value = {"status": "ok"}
|
|
714
|
+
|
|
715
|
+
result = process_data()
|
|
716
|
+
|
|
717
|
+
assert result["status"] == "ok"
|
|
718
|
+
|
|
719
|
+
# ✅ 异步 Mock
|
|
720
|
+
async def test_async_function():
|
|
721
|
+
mock_fetch = AsyncMock(return_value={"data": "test"})
|
|
722
|
+
|
|
723
|
+
with patch("myapp.client.fetch", mock_fetch):
|
|
724
|
+
result = await get_data()
|
|
725
|
+
|
|
726
|
+
assert result == {"data": "test"}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
### 测试组织
|
|
730
|
+
|
|
731
|
+
```python
|
|
732
|
+
# ✅ 使用类组织相关测试
|
|
733
|
+
class TestUserAuthentication:
|
|
734
|
+
"""用户认证相关测试"""
|
|
735
|
+
|
|
736
|
+
def test_login_with_valid_credentials(self, user):
|
|
737
|
+
assert authenticate(user.email, "password") is True
|
|
738
|
+
|
|
739
|
+
def test_login_with_invalid_password(self, user):
|
|
740
|
+
assert authenticate(user.email, "wrong") is False
|
|
741
|
+
|
|
742
|
+
def test_login_locks_after_failed_attempts(self, user):
|
|
743
|
+
for _ in range(5):
|
|
744
|
+
authenticate(user.email, "wrong")
|
|
745
|
+
assert user.is_locked is True
|
|
746
|
+
|
|
747
|
+
# ✅ 使用 mark 标记测试
|
|
748
|
+
@pytest.mark.slow
|
|
749
|
+
def test_large_data_processing():
|
|
750
|
+
pass
|
|
751
|
+
|
|
752
|
+
@pytest.mark.integration
|
|
753
|
+
def test_database_connection():
|
|
754
|
+
pass
|
|
755
|
+
|
|
756
|
+
# 运行特定标记的测试:pytest -m "not slow"
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
### 覆盖率与质量
|
|
760
|
+
|
|
761
|
+
```python
|
|
762
|
+
# pytest.ini 或 pyproject.toml
|
|
763
|
+
[tool.pytest.ini_options]
|
|
764
|
+
addopts = "--cov=myapp --cov-report=term-missing --cov-fail-under=80"
|
|
765
|
+
testpaths = ["tests"]
|
|
766
|
+
|
|
767
|
+
# ✅ 测试边界情况
|
|
768
|
+
def test_empty_input():
|
|
769
|
+
assert process([]) == []
|
|
770
|
+
|
|
771
|
+
def test_none_input():
|
|
772
|
+
with pytest.raises(TypeError):
|
|
773
|
+
process(None)
|
|
774
|
+
|
|
775
|
+
def test_large_input():
|
|
776
|
+
large_data = list(range(100000))
|
|
777
|
+
result = process(large_data)
|
|
778
|
+
assert len(result) == 100000
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
---
|
|
782
|
+
|
|
783
|
+
## 性能优化
|
|
784
|
+
|
|
785
|
+
### 数据结构选择
|
|
786
|
+
|
|
787
|
+
```python
|
|
788
|
+
# ❌ 列表查找 O(n)
|
|
789
|
+
if item in large_list: # 慢
|
|
790
|
+
pass
|
|
791
|
+
|
|
792
|
+
# ✅ 集合查找 O(1)
|
|
793
|
+
large_set = set(large_list)
|
|
794
|
+
if item in large_set: # 快
|
|
795
|
+
pass
|
|
796
|
+
|
|
797
|
+
# ✅ 使用 collections 模块
|
|
798
|
+
from collections import Counter, defaultdict, deque
|
|
799
|
+
|
|
800
|
+
# 计数
|
|
801
|
+
word_counts = Counter(words)
|
|
802
|
+
most_common = word_counts.most_common(10)
|
|
803
|
+
|
|
804
|
+
# 默认字典
|
|
805
|
+
graph = defaultdict(list)
|
|
806
|
+
graph[node].append(neighbor)
|
|
807
|
+
|
|
808
|
+
# 双端队列(两端操作 O(1))
|
|
809
|
+
queue = deque()
|
|
810
|
+
queue.appendleft(item) # O(1) vs list.insert(0, item) O(n)
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
### 生成器与迭代器
|
|
814
|
+
|
|
815
|
+
```python
|
|
816
|
+
# ❌ 一次性加载所有数据
|
|
817
|
+
def get_all_users():
|
|
818
|
+
return [User(row) for row in db.fetch_all()] # 内存占用大
|
|
819
|
+
|
|
820
|
+
# ✅ 使用生成器
|
|
821
|
+
def get_all_users():
|
|
822
|
+
for row in db.fetch_all():
|
|
823
|
+
yield User(row) # 懒加载
|
|
824
|
+
|
|
825
|
+
# ✅ 生成器表达式
|
|
826
|
+
sum_of_squares = sum(x**2 for x in range(1000000)) # 不创建列表
|
|
827
|
+
|
|
828
|
+
# ✅ itertools 模块
|
|
829
|
+
from itertools import islice, chain, groupby
|
|
830
|
+
|
|
831
|
+
# 只取前 10 个
|
|
832
|
+
first_10 = list(islice(infinite_generator(), 10))
|
|
833
|
+
|
|
834
|
+
# 链接多个迭代器
|
|
835
|
+
all_items = chain(list1, list2, list3)
|
|
836
|
+
|
|
837
|
+
# 分组
|
|
838
|
+
for key, group in groupby(sorted(items, key=get_key), key=get_key):
|
|
839
|
+
process_group(key, list(group))
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
### 缓存
|
|
843
|
+
|
|
844
|
+
```python
|
|
845
|
+
from functools import lru_cache, cache
|
|
846
|
+
|
|
847
|
+
# ✅ LRU 缓存
|
|
848
|
+
@lru_cache(maxsize=128)
|
|
849
|
+
def expensive_computation(n: int) -> int:
|
|
850
|
+
return sum(i**2 for i in range(n))
|
|
851
|
+
|
|
852
|
+
# ✅ 无限缓存(Python 3.9+)
|
|
853
|
+
@cache
|
|
854
|
+
def fibonacci(n: int) -> int:
|
|
855
|
+
if n < 2:
|
|
856
|
+
return n
|
|
857
|
+
return fibonacci(n - 1) + fibonacci(n - 2)
|
|
858
|
+
|
|
859
|
+
# ✅ 手动缓存(需要更多控制时)
|
|
860
|
+
class DataService:
|
|
861
|
+
def __init__(self):
|
|
862
|
+
self._cache: dict[str, Any] = {}
|
|
863
|
+
self._cache_ttl: dict[str, float] = {}
|
|
864
|
+
|
|
865
|
+
def get_data(self, key: str) -> Any:
|
|
866
|
+
if key in self._cache:
|
|
867
|
+
if time.time() < self._cache_ttl[key]:
|
|
868
|
+
return self._cache[key]
|
|
869
|
+
|
|
870
|
+
data = self._fetch_data(key)
|
|
871
|
+
self._cache[key] = data
|
|
872
|
+
self._cache_ttl[key] = time.time() + 300 # 5 分钟
|
|
873
|
+
return data
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
### 并行处理
|
|
877
|
+
|
|
878
|
+
```python
|
|
879
|
+
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
|
|
880
|
+
|
|
881
|
+
# ✅ IO 密集型使用线程池
|
|
882
|
+
def fetch_all_urls(urls: list[str]) -> list[str]:
|
|
883
|
+
with ThreadPoolExecutor(max_workers=10) as executor:
|
|
884
|
+
results = list(executor.map(fetch_url, urls))
|
|
885
|
+
return results
|
|
886
|
+
|
|
887
|
+
# ✅ CPU 密集型使用进程池
|
|
888
|
+
def process_large_dataset(data: list) -> list:
|
|
889
|
+
with ProcessPoolExecutor() as executor:
|
|
890
|
+
results = list(executor.map(heavy_computation, data))
|
|
891
|
+
return results
|
|
892
|
+
|
|
893
|
+
# ✅ 使用 as_completed 获取最先完成的结果
|
|
894
|
+
from concurrent.futures import as_completed
|
|
895
|
+
|
|
896
|
+
with ThreadPoolExecutor() as executor:
|
|
897
|
+
futures = {executor.submit(fetch, url): url for url in urls}
|
|
898
|
+
for future in as_completed(futures):
|
|
899
|
+
url = futures[future]
|
|
900
|
+
try:
|
|
901
|
+
result = future.result()
|
|
902
|
+
except Exception as e:
|
|
903
|
+
print(f"{url} failed: {e}")
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
---
|
|
907
|
+
|
|
908
|
+
## 代码风格
|
|
909
|
+
|
|
910
|
+
### PEP 8 要点
|
|
911
|
+
|
|
912
|
+
```python
|
|
913
|
+
# ✅ 命名规范
|
|
914
|
+
class MyClass: # 类名 PascalCase
|
|
915
|
+
MAX_SIZE = 100 # 常量 UPPER_SNAKE_CASE
|
|
916
|
+
|
|
917
|
+
def method_name(self): # 方法 snake_case
|
|
918
|
+
local_var = 1 # 变量 snake_case
|
|
919
|
+
|
|
920
|
+
# ✅ 导入顺序
|
|
921
|
+
# 1. 标准库
|
|
922
|
+
import os
|
|
923
|
+
import sys
|
|
924
|
+
from typing import Optional
|
|
925
|
+
|
|
926
|
+
# 2. 第三方库
|
|
927
|
+
import numpy as np
|
|
928
|
+
import pandas as pd
|
|
929
|
+
|
|
930
|
+
# 3. 本地模块
|
|
931
|
+
from myapp import config
|
|
932
|
+
from myapp.utils import helper
|
|
933
|
+
|
|
934
|
+
# ✅ 行长度限制(79 或 88 字符)
|
|
935
|
+
# 长表达式的换行
|
|
936
|
+
result = (
|
|
937
|
+
long_function_name(arg1, arg2, arg3)
|
|
938
|
+
+ another_long_function(arg4, arg5)
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
# ✅ 空行规范
|
|
942
|
+
class MyClass:
|
|
943
|
+
"""类文档字符串"""
|
|
944
|
+
|
|
945
|
+
def method_one(self):
|
|
946
|
+
pass
|
|
947
|
+
|
|
948
|
+
def method_two(self): # 方法间一个空行
|
|
949
|
+
pass
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
def top_level_function(): # 顶层定义间两个空行
|
|
953
|
+
pass
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
### 文档字符串
|
|
957
|
+
|
|
958
|
+
```python
|
|
959
|
+
# ✅ Google 风格文档字符串
|
|
960
|
+
def calculate_area(width: float, height: float) -> float:
|
|
961
|
+
"""计算矩形面积。
|
|
962
|
+
|
|
963
|
+
Args:
|
|
964
|
+
width: 矩形的宽度(必须为正数)。
|
|
965
|
+
height: 矩形的高度(必须为正数)。
|
|
966
|
+
|
|
967
|
+
Returns:
|
|
968
|
+
矩形的面积。
|
|
969
|
+
|
|
970
|
+
Raises:
|
|
971
|
+
ValueError: 如果 width 或 height 为负数。
|
|
972
|
+
|
|
973
|
+
Example:
|
|
974
|
+
>>> calculate_area(3, 4)
|
|
975
|
+
12.0
|
|
976
|
+
"""
|
|
977
|
+
if width < 0 or height < 0:
|
|
978
|
+
raise ValueError("Dimensions must be positive")
|
|
979
|
+
return width * height
|
|
980
|
+
|
|
981
|
+
# ✅ 类文档字符串
|
|
982
|
+
class DataProcessor:
|
|
983
|
+
"""处理和转换数据的工具类。
|
|
984
|
+
|
|
985
|
+
Attributes:
|
|
986
|
+
source: 数据来源路径。
|
|
987
|
+
format: 输出格式('json' 或 'csv')。
|
|
988
|
+
|
|
989
|
+
Example:
|
|
990
|
+
>>> processor = DataProcessor("data.csv")
|
|
991
|
+
>>> processor.process()
|
|
992
|
+
"""
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
### 现代 Python 特性
|
|
996
|
+
|
|
997
|
+
```python
|
|
998
|
+
# ✅ f-string(Python 3.6+)
|
|
999
|
+
name = "World"
|
|
1000
|
+
print(f"Hello, {name}!")
|
|
1001
|
+
|
|
1002
|
+
# 带表达式
|
|
1003
|
+
print(f"Result: {1 + 2 = }") # "Result: 1 + 2 = 3"
|
|
1004
|
+
|
|
1005
|
+
# ✅ 海象运算符(Python 3.8+)
|
|
1006
|
+
if (n := len(items)) > 10:
|
|
1007
|
+
print(f"List has {n} items")
|
|
1008
|
+
|
|
1009
|
+
# ✅ 位置参数分隔符(Python 3.8+)
|
|
1010
|
+
def greet(name, /, greeting="Hello", *, punctuation="!"):
|
|
1011
|
+
"""name 只能位置传参,punctuation 只能关键字传参"""
|
|
1012
|
+
return f"{greeting}, {name}{punctuation}"
|
|
1013
|
+
|
|
1014
|
+
# ✅ 模式匹配(Python 3.10+)
|
|
1015
|
+
def handle_response(response: dict):
|
|
1016
|
+
match response:
|
|
1017
|
+
case {"status": "ok", "data": data}:
|
|
1018
|
+
return process_data(data)
|
|
1019
|
+
case {"status": "error", "message": msg}:
|
|
1020
|
+
raise APIError(msg)
|
|
1021
|
+
case _:
|
|
1022
|
+
raise ValueError("Unknown response format")
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
---
|
|
1026
|
+
|
|
1027
|
+
## Review Checklist
|
|
1028
|
+
|
|
1029
|
+
### 类型安全
|
|
1030
|
+
- [ ] 函数有类型注解(参数和返回值)
|
|
1031
|
+
- [ ] 使用 `Optional` 明确可能为 None
|
|
1032
|
+
- [ ] 泛型类型正确使用
|
|
1033
|
+
- [ ] mypy 检查通过(无错误)
|
|
1034
|
+
- [ ] 避免使用 `Any`,必要时添加注释说明
|
|
1035
|
+
|
|
1036
|
+
### 异步代码
|
|
1037
|
+
- [ ] async/await 正确配对使用
|
|
1038
|
+
- [ ] 没有在异步代码中使用阻塞调用
|
|
1039
|
+
- [ ] 正确处理 `CancelledError`
|
|
1040
|
+
- [ ] 使用 `asyncio.gather` 或 `TaskGroup` 并发执行
|
|
1041
|
+
- [ ] 资源正确清理(async context manager)
|
|
1042
|
+
|
|
1043
|
+
### 异常处理
|
|
1044
|
+
- [ ] 捕获特定异常类型,不使用裸 `except:`
|
|
1045
|
+
- [ ] 异常链使用 `from` 保留原因
|
|
1046
|
+
- [ ] 自定义异常继承自合适的基类
|
|
1047
|
+
- [ ] 异常信息有意义,便于调试
|
|
1048
|
+
|
|
1049
|
+
### 数据结构
|
|
1050
|
+
- [ ] 没有使用可变默认参数(list、dict、set)
|
|
1051
|
+
- [ ] 类属性不是可变对象
|
|
1052
|
+
- [ ] 选择正确的数据结构(set vs list 查找)
|
|
1053
|
+
- [ ] 大数据集使用生成器而非列表
|
|
1054
|
+
|
|
1055
|
+
### 测试
|
|
1056
|
+
- [ ] 测试覆盖率达标(建议 ≥80%)
|
|
1057
|
+
- [ ] 测试命名清晰描述测试场景
|
|
1058
|
+
- [ ] 边界情况有测试覆盖
|
|
1059
|
+
- [ ] Mock 正确隔离外部依赖
|
|
1060
|
+
- [ ] 异步代码有对应的异步测试
|
|
1061
|
+
|
|
1062
|
+
### 代码风格
|
|
1063
|
+
- [ ] 遵循 PEP 8 风格指南
|
|
1064
|
+
- [ ] 函数和类有 docstring
|
|
1065
|
+
- [ ] 导入顺序正确(标准库、第三方、本地)
|
|
1066
|
+
- [ ] 命名一致且有意义
|
|
1067
|
+
- [ ] 使用现代 Python 特性(f-string、walrus operator 等)
|
|
1068
|
+
|
|
1069
|
+
### 性能
|
|
1070
|
+
- [ ] 避免循环中重复创建对象
|
|
1071
|
+
- [ ] 字符串拼接使用 join
|
|
1072
|
+
- [ ] 合理使用缓存(@lru_cache)
|
|
1073
|
+
- [ ] IO/CPU 密集型使用合适的并行方式
|