Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:15:04 +08:00
commit ec0d1b5905
19 changed files with 5696 additions and 0 deletions

View File

@@ -0,0 +1,259 @@
# Advanced Type Annotations Reference
This document provides detailed guidance on advanced type annotation patterns in Python.
## Union Types
Use `|` (union operator) for Python 3.10+ or `Union` for earlier versions:
```python
# Python 3.10+
def process(value: int | str) -> None:
...
# Python 3.9 and earlier
from typing import Union
def process(value: Union[int, str]) -> None:
...
```
## Optional Types
`Optional[X]` is shorthand for `X | None`:
```python
from typing import Optional
# These are equivalent:
def foo(x: Optional[int]) -> None: ...
def foo(x: int | None) -> None: ... # Preferred in Python 3.10+
```
## Callable Types
For function types, use `Callable`:
```python
from collections.abc import Callable
def apply_func(func: Callable[[int, int], int], x: int, y: int) -> int:
return func(x, y)
# Callable[[arg1_type, arg2_type], return_type]
```
For functions with variable arguments:
```python
# Use ... for variable arguments
def accepts_any_callable(func: Callable[..., int]) -> None:
...
```
## Sequence, Mapping, and Iterable
Use abstract types from `collections.abc` when you don't need specific container features:
```python
from collections.abc import Sequence, Mapping, Iterable
def process_items(items: Sequence[str]) -> None:
"""Works with lists, tuples, or any sequence."""
...
def process_mapping(data: Mapping[str, int]) -> None:
"""Works with dicts or any mapping."""
...
def sum_numbers(nums: Iterable[int]) -> int:
"""Works with any iterable."""
return sum(nums)
```
## Protocol and Structural Subtyping
Define structural types using `Protocol`:
```python
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None:
...
def render(obj: Drawable) -> None:
obj.draw() # Any object with a draw() method works
```
## TypedDict for Structured Dictionaries
Use `TypedDict` for dictionaries with known keys:
```python
from typing import TypedDict
class Employee(TypedDict):
name: str
id: int
department: str
def process_employee(emp: Employee) -> None:
print(emp["name"]) # Type checker knows this key exists
```
Optional fields:
```python
from typing import TypedDict, NotRequired
class Employee(TypedDict):
name: str
id: int
department: NotRequired[str] # Optional field
```
## Literal Types
Use `Literal` for specific values:
```python
from typing import Literal
def set_mode(mode: Literal["read", "write", "append"]) -> None:
...
# Type checker ensures only these values are passed
set_mode("read") # OK
set_mode("delete") # Error
```
## Generic Classes
Create generic classes with `Generic`:
```python
from typing import Generic, TypeVar
T = TypeVar("T")
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
# Usage
int_stack: Stack[int] = Stack()
int_stack.push(42)
```
## ParamSpec for Higher-Order Functions
Use `ParamSpec` to preserve function signatures:
```python
from typing import ParamSpec, TypeVar, Callable
P = ParamSpec("P")
R = TypeVar("R")
def log_calls(func: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_calls
def greet(name: str, excited: bool = False) -> str:
return f"Hello, {name}{'!' if excited else '.'}"
# Type checker preserves the signature of greet
```
## TypeGuard for Type Narrowing
Use `TypeGuard` for custom type checking functions:
```python
from typing import TypeGuard
def is_str_list(val: list[object]) -> TypeGuard[list[str]]:
return all(isinstance(x, str) for x in val)
def process(items: list[object]) -> None:
if is_str_list(items):
# Type checker knows items is list[str] here
print(", ".join(items))
```
## Annotating *args and **kwargs
```python
def foo(*args: int, **kwargs: str) -> None:
# args is tuple[int, ...]
# kwargs is dict[str, str]
...
```
## Overload for Multiple Signatures
Use `@overload` for functions with different return types based on arguments:
```python
from typing import overload
@overload
def process(x: int) -> int: ...
@overload
def process(x: str) -> str: ...
def process(x: int | str) -> int | str:
if isinstance(x, int):
return x * 2
return x.upper()
```
## Self Type (Python 3.11+)
Use `Self` for methods that return the instance:
```python
from typing import Self
class Builder:
def add_item(self, item: str) -> Self:
self.items.append(item)
return self # Return type is automatically the class type
def build(self) -> dict:
return {"items": self.items}
```
For Python < 3.11, use TypeVar:
```python
from typing import TypeVar
TBuilder = TypeVar("TBuilder", bound="Builder")
class Builder:
def add_item(self: TBuilder, item: str) -> TBuilder:
self.items.append(item)
return self
```
## Best Practices
1. Use the most general type that works (e.g., `Sequence` over `list`)
2. Use `Protocol` for duck typing
3. Use `TypedDict` for structured dictionaries
4. Use `Literal` to restrict to specific values
5. Use `TypeGuard` for custom type narrowing
6. Always annotate public APIs
7. Use `Any` sparingly and explicitly when needed
8. Prefer built-in generic types (`list`, `dict`) over `typing` equivalents (Python 3.9+)

View File

@@ -0,0 +1,361 @@
# Python Anti-Patterns and Fixes
Common Python mistakes and their corrections.
## 1. Mutable Default Arguments
**Anti-pattern:**
```python
def add_item(item, items=[]): # WRONG
items.append(item)
return items
```
**Why it's wrong:** The list is created once when the function is defined, not each time it's called.
**Fix:**
```python
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
```
## 2. Bare Except Clauses
**Anti-pattern:**
```python
try:
risky_operation()
except: # WRONG - catches everything, including KeyboardInterrupt
handle_error()
```
**Fix:**
```python
try:
risky_operation()
except Exception as e: # Or specific exception types
logger.error(f"Operation failed: {e}")
handle_error()
```
## 3. Using == for None Comparisons
**Anti-pattern:**
```python
if value == None: # WRONG
...
```
**Fix:**
```python
if value is None:
...
```
**Why:** `is` checks identity, `==` checks equality. `None` is a singleton.
## 4. Comparing Boolean Values Explicitly
**Anti-pattern:**
```python
if flag == True: # WRONG
...
if len(items) > 0: # WRONG
...
```
**Fix:**
```python
if flag:
...
if items:
...
```
## 5. Not Using Context Managers for Files
**Anti-pattern:**
```python
f = open("file.txt") # WRONG - file may not close if error occurs
data = f.read()
f.close()
```
**Fix:**
```python
with open("file.txt") as f:
data = f.read()
```
## 6. String Concatenation in Loops
**Anti-pattern:**
```python
result = ""
for item in items:
result += str(item) # WRONG - creates new string each iteration
```
**Fix:**
```python
result = "".join(str(item) for item in items)
```
## 7. Modifying List While Iterating
**Anti-pattern:**
```python
for item in items:
if should_remove(item):
items.remove(item) # WRONG - skips elements
```
**Fix:**
```python
items = [item for item in items if not should_remove(item)]
# Or
items[:] = [item for item in items if not should_remove(item)]
```
## 8. Using eval() or exec()
**Anti-pattern:**
```python
user_input = get_user_input()
result = eval(user_input) # WRONG - major security risk
```
**Fix:**
```python
import ast
result = ast.literal_eval(user_input) # Only evaluates literals
```
## 9. Not Using enumerate()
**Anti-pattern:**
```python
i = 0
for item in items:
print(f"{i}: {item}")
i += 1
```
**Fix:**
```python
for i, item in enumerate(items):
print(f"{i}: {item}")
```
## 10. Creating Empty Lists/Dicts Unnecessarily
**Anti-pattern:**
```python
items = []
items.append(1)
items.append(2)
items.append(3)
```
**Fix:**
```python
items = [1, 2, 3]
```
## 11. Not Using dict.get() with Defaults
**Anti-pattern:**
```python
if key in my_dict:
value = my_dict[key]
else:
value = default
```
**Fix:**
```python
value = my_dict.get(key, default)
```
## 12. Using range(len()) Instead of enumerate()
**Anti-pattern:**
```python
for i in range(len(items)):
item = items[i]
print(f"{i}: {item}")
```
**Fix:**
```python
for i, item in enumerate(items):
print(f"{i}: {item}")
```
## 13. Not Using Collections Module
**Anti-pattern:**
```python
word_counts = {}
for word in words:
if word in word_counts:
word_counts[word] += 1
else:
word_counts[word] = 1
```
**Fix:**
```python
from collections import Counter
word_counts = Counter(words)
```
## 14. Not Using defaultdict
**Anti-pattern:**
```python
groups = {}
for item in items:
key = get_key(item)
if key not in groups:
groups[key] = []
groups[key].append(item)
```
**Fix:**
```python
from collections import defaultdict
groups = defaultdict(list)
for item in items:
key = get_key(item)
groups[key].append(item)
```
## 15. Overly Complex Comprehensions
**Anti-pattern:**
```python
result = [
transform(x)
for x in items
if condition1(x)
if condition2(x)
if condition3(x)
for y in x.sub_items
if condition4(y)
] # WRONG - too complex
```
**Fix:**
```python
result = []
for x in items:
if condition1(x) and condition2(x) and condition3(x):
for y in x.sub_items:
if condition4(y):
result.append(transform(x))
```
## 16. Not Using Path Objects
**Anti-pattern:**
```python
import os
path = os.path.join(dir_name, "file.txt")
if os.path.exists(path):
with open(path) as f:
...
```
**Fix:**
```python
from pathlib import Path
path = Path(dir_name) / "file.txt"
if path.exists():
with path.open() as f:
...
```
## 17. String Formatting with + or %
**Anti-pattern:**
```python
message = "Hello, " + name + "! You have " + str(count) + " messages."
message = "Hello, %s! You have %d messages." % (name, count)
```
**Fix:**
```python
message = f"Hello, {name}! You have {count} messages."
```
## 18. Not Using dataclasses
**Anti-pattern:**
```python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
def __eq__(self, other):
return self.x == other.x and self.y == other.y
```
**Fix:**
```python
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
```
## 19. Lambda Abuse
**Anti-pattern:**
```python
process = lambda x: x.strip().lower().replace(" ", "_")[:20] # WRONG
```
**Fix:**
```python
def process(x: str) -> str:
"""Clean and truncate string."""
return x.strip().lower().replace(" ", "_")[:20]
```
## 20. Not Using Sets for Membership Testing
**Anti-pattern:**
```python
valid_codes = ["A1", "A2", "A3", ...] # Long list
if code in valid_codes: # O(n) lookup
...
```
**Fix:**
```python
valid_codes = {"A1", "A2", "A3", ...} # Set
if code in valid_codes: # O(1) lookup
...
```
## Summary
Key principles to avoid anti-patterns:
1. Use built-in functions and standard library when possible
2. Leverage context managers for resource management
3. Use appropriate data structures (sets for membership, Counter for counting)
4. Keep code readable and idiomatic
5. Use modern Python features (f-strings, dataclasses, Path)
6. Avoid premature optimization
7. Write explicit, clear code over clever code

View File

@@ -0,0 +1,384 @@
# Docstring Examples
Complete examples of Google-style docstrings for various Python constructs.
## Module Docstring
```python
"""This is an example module docstring.
This module provides utilities for processing user data. It includes functions
for validation, transformation, and persistence of user information.
Typical usage example:
user = create_user("John Doe", "john@example.com")
validate_user(user)
save_user(user)
"""
```
## Function Docstrings
### Simple Function
```python
def greet(name: str) -> str:
"""Returns a greeting message.
Args:
name: The name of the person to greet.
Returns:
A greeting string.
"""
return f"Hello, {name}!"
```
### Function with Multiple Arguments
```python
def calculate_total(
price: float,
quantity: int,
discount: float = 0.0,
tax_rate: float = 0.0
) -> float:
"""Calculates the total cost including discount and tax.
Args:
price: The unit price of the item.
quantity: The number of items.
discount: The discount as a decimal (e.g., 0.1 for 10% off).
Defaults to 0.0.
tax_rate: The tax rate as a decimal (e.g., 0.08 for 8% tax).
Defaults to 0.0.
Returns:
The total cost after applying discount and tax.
Raises:
ValueError: If price or quantity is negative.
"""
if price < 0 or quantity < 0:
raise ValueError("Price and quantity must be non-negative")
subtotal = price * quantity * (1 - discount)
return subtotal * (1 + tax_rate)
```
### Function with Complex Return Type
```python
def parse_config(
config_path: str
) -> tuple[dict[str, str], list[str]]:
"""Parses a configuration file.
Args:
config_path: Path to the configuration file.
Returns:
A tuple containing:
- A dictionary of configuration key-value pairs.
- A list of warning messages encountered during parsing.
Raises:
FileNotFoundError: If the config file doesn't exist.
ValueError: If the config file is malformed.
"""
...
```
### Function with Side Effects
```python
def update_database(
user_id: int,
data: dict[str, Any]
) -> None:
"""Updates user data in the database.
Note:
This function modifies the database directly. Ensure proper
transaction handling in the calling code.
Args:
user_id: The ID of the user to update.
data: Dictionary containing fields to update.
Raises:
DatabaseError: If the database operation fails.
ValueError: If user_id is invalid or data is empty.
"""
...
```
## Class Docstrings
### Simple Class
```python
class User:
"""Represents a user in the system.
Attributes:
username: The user's unique username.
email: The user's email address.
created_at: Timestamp when the user was created.
"""
def __init__(self, username: str, email: str):
"""Initializes a new User.
Args:
username: The desired username.
email: The user's email address.
"""
self.username = username
self.email = email
self.created_at = datetime.now()
```
### Complex Class with Properties
```python
class Rectangle:
"""Represents a rectangle with width and height.
This class provides methods for calculating area and perimeter,
and properties for accessing dimensions.
Attributes:
width: The width of the rectangle.
height: The height of the rectangle.
Example:
>>> rect = Rectangle(10, 5)
>>> rect.area
50
>>> rect.perimeter
30
"""
def __init__(self, width: float, height: float):
"""Initializes a Rectangle.
Args:
width: The width of the rectangle. Must be positive.
height: The height of the rectangle. Must be positive.
Raises:
ValueError: If width or height is not positive.
"""
if width <= 0 or height <= 0:
raise ValueError("Width and height must be positive")
self._width = width
self._height = height
@property
def width(self) -> float:
"""Gets the width of the rectangle."""
return self._width
@width.setter
def width(self, value: float) -> None:
"""Sets the width of the rectangle.
Args:
value: The new width. Must be positive.
Raises:
ValueError: If value is not positive.
"""
if value <= 0:
raise ValueError("Width must be positive")
self._width = value
@property
def area(self) -> float:
"""Calculates and returns the area of the rectangle."""
return self._width * self._height
@property
def perimeter(self) -> float:
"""Calculates and returns the perimeter of the rectangle."""
return 2 * (self._width + self._height)
```
## Generator Functions
```python
def fibonacci(n: int) -> Iterator[int]:
"""Generates the first n Fibonacci numbers.
Args:
n: The number of Fibonacci numbers to generate.
Yields:
The next Fibonacci number in the sequence.
Raises:
ValueError: If n is negative.
Example:
>>> list(fibonacci(5))
[0, 1, 1, 2, 3]
"""
if n < 0:
raise ValueError("n must be non-negative")
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
```
## Exception Classes
```python
class InvalidUserError(Exception):
"""Raised when user data is invalid.
This exception is raised during user validation when the provided
data doesn't meet the required criteria.
Attributes:
username: The invalid username that caused the error.
message: Explanation of the validation failure.
"""
def __init__(self, username: str, message: str):
"""Initializes the exception.
Args:
username: The username that failed validation.
message: Description of why validation failed.
"""
self.username = username
self.message = message
super().__init__(f"{username}: {message}")
```
## Context Manager
```python
class DatabaseConnection:
"""Context manager for database connections.
Automatically handles connection setup and teardown.
Example:
>>> with DatabaseConnection("localhost", 5432) as conn:
... conn.execute("SELECT * FROM users")
"""
def __init__(self, host: str, port: int):
"""Initializes the database connection parameters.
Args:
host: The database host address.
port: The database port number.
"""
self.host = host
self.port = port
self._connection = None
def __enter__(self) -> "DatabaseConnection":
"""Establishes the database connection.
Returns:
The DatabaseConnection instance.
Raises:
ConnectionError: If connection cannot be established.
"""
self._connection = create_connection(self.host, self.port)
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
"""Closes the database connection.
Args:
exc_type: The exception type, if an exception occurred.
exc_val: The exception value, if an exception occurred.
exc_tb: The exception traceback, if an exception occurred.
Returns:
False to propagate exceptions, True to suppress them.
"""
if self._connection:
self._connection.close()
return False
```
## Async Functions
```python
async def fetch_data(url: str, timeout: float = 30.0) -> dict[str, Any]:
"""Asynchronously fetches data from a URL.
Args:
url: The URL to fetch data from.
timeout: Maximum time to wait for response in seconds.
Defaults to 30.0.
Returns:
A dictionary containing the fetched data.
Raises:
aiohttp.ClientError: If the request fails.
asyncio.TimeoutError: If the request times out.
Example:
>>> data = await fetch_data("https://api.example.com/data")
"""
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=timeout) as response:
return await response.json()
```
## Test Functions
```python
def test_user_creation():
"""Tests that User objects are created correctly.
This test verifies:
- Username is set correctly
- Email is set correctly
- created_at is set to current time
"""
user = User("john_doe", "john@example.com")
assert user.username == "john_doe"
assert user.email == "john@example.com"
assert isinstance(user.created_at, datetime)
```
## Docstring Sections
Common sections in Google-style docstrings:
- **Args:** Function/method parameters
- **Returns:** Return value description
- **Yields:** For generator functions
- **Raises:** Exceptions that may be raised
- **Attributes:** For classes, describes instance attributes
- **Example:** Usage examples
- **Note:** Important notes or warnings
- **Warning:** Critical warnings
- **Todo:** Planned improvements
- **See Also:** Related functions or classes
## Style Guidelines
1. Use triple double quotes (`"""`) for all docstrings
2. First line is a brief summary (one sentence, no period needed if one line)
3. Leave a blank line before sections (Args, Returns, etc.)
4. Capitalize section headers
5. Use imperative mood ("Returns" not "Return")
6. Be specific and concise
7. Include type information in Args and Returns when not obvious from annotations
8. Always document exceptions that can be raised
9. Include examples for complex functions
10. Keep line length under 80 characters where possible