@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.

Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/dist/providers/data/.manifest.json +1 -1
  3. package/dist/skills/code-review/LICENSE +21 -0
  4. package/dist/skills/code-review/SKILL.md +233 -0
  5. package/dist/skills/code-review/assets/pr-review-template.md +137 -0
  6. package/dist/skills/code-review/assets/review-checklist.md +123 -0
  7. package/dist/skills/code-review/reference/angular.md +768 -0
  8. package/dist/skills/code-review/reference/architecture-review-guide.md +472 -0
  9. package/dist/skills/code-review/reference/c.md +890 -0
  10. package/dist/skills/code-review/reference/code-quality-universal.md +488 -0
  11. package/dist/skills/code-review/reference/code-review-best-practices.md +136 -0
  12. package/dist/skills/code-review/reference/common-bugs-checklist.md +302 -0
  13. package/dist/skills/code-review/reference/cpp.md +893 -0
  14. package/dist/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
  15. package/dist/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
  16. package/dist/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
  17. package/dist/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
  18. package/dist/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
  19. package/dist/skills/code-review/reference/csharp.md +525 -0
  20. package/dist/skills/code-review/reference/css-less-sass.md +661 -0
  21. package/dist/skills/code-review/reference/dart.md +670 -0
  22. package/dist/skills/code-review/reference/django.md +985 -0
  23. package/dist/skills/code-review/reference/fastapi.md +580 -0
  24. package/dist/skills/code-review/reference/go.md +993 -0
  25. package/dist/skills/code-review/reference/java.md +409 -0
  26. package/dist/skills/code-review/reference/java8.md +586 -0
  27. package/dist/skills/code-review/reference/kotlin.md +1018 -0
  28. package/dist/skills/code-review/reference/nestjs.md +593 -0
  29. package/dist/skills/code-review/reference/performance-review-guide.md +816 -0
  30. package/dist/skills/code-review/reference/php.md +684 -0
  31. package/dist/skills/code-review/reference/python.md +1073 -0
  32. package/dist/skills/code-review/reference/qt.md +757 -0
  33. package/dist/skills/code-review/reference/react.md +871 -0
  34. package/dist/skills/code-review/reference/ruby.md +964 -0
  35. package/dist/skills/code-review/reference/rust.md +846 -0
  36. package/dist/skills/code-review/reference/security-review-guide.md +494 -0
  37. package/dist/skills/code-review/reference/svelte.md +1064 -0
  38. package/dist/skills/code-review/reference/swift.md +936 -0
  39. package/dist/skills/code-review/reference/typescript.md +1016 -0
  40. package/dist/skills/code-review/reference/vue.md +924 -0
  41. package/dist/skills/code-review/reference/zig.md +440 -0
  42. package/dist/skills/code-review/scripts/pr-analyzer.py +435 -0
  43. package/dist/skills/code-review/scripts/test_pr_analyzer.py +380 -0
  44. package/dist/yorn.cjs +628 -628
  45. 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 密集型使用合适的并行方式