Initial commit
This commit is contained in:
259
skills/python-style-guide/references/advanced_types.md
Normal file
259
skills/python-style-guide/references/advanced_types.md
Normal 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+)
|
||||
361
skills/python-style-guide/references/antipatterns.md
Normal file
361
skills/python-style-guide/references/antipatterns.md
Normal 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
|
||||
384
skills/python-style-guide/references/docstring_examples.md
Normal file
384
skills/python-style-guide/references/docstring_examples.md
Normal 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
|
||||
Reference in New Issue
Block a user