225 lines
3.9 KiB
Markdown
225 lines
3.9 KiB
Markdown
|
|
# Python Language Rules
|
||
|
|
|
||
|
|
## Toolchain
|
||
|
|
|
||
|
|
### Package Management (uv)
|
||
|
|
```bash
|
||
|
|
uv init my-project --package
|
||
|
|
uv add numpy pandas
|
||
|
|
uv add --dev pytest ruff pyright hypothesis
|
||
|
|
uv run python -m pytest
|
||
|
|
uv lock --upgrade-package numpy
|
||
|
|
```
|
||
|
|
|
||
|
|
### Linting & Formatting (ruff)
|
||
|
|
```toml
|
||
|
|
[tool.ruff]
|
||
|
|
line-length = 100
|
||
|
|
target-version = "py311"
|
||
|
|
|
||
|
|
[tool.ruff.lint]
|
||
|
|
select = ["E", "F", "W", "I", "N", "UP"]
|
||
|
|
ignore = ["E501"]
|
||
|
|
|
||
|
|
[tool.ruff.format]
|
||
|
|
quote-style = "double"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Type Checking (pyright)
|
||
|
|
```toml
|
||
|
|
[tool.pyright]
|
||
|
|
typeCheckingMode = "strict"
|
||
|
|
reportMissingTypeStubs = true
|
||
|
|
reportUnknownMemberType = true
|
||
|
|
```
|
||
|
|
|
||
|
|
### Testing (pytest + hypothesis)
|
||
|
|
```python
|
||
|
|
import pytest
|
||
|
|
from hypothesis import given, strategies as st
|
||
|
|
|
||
|
|
@given(st.integers(), st.integers())
|
||
|
|
def test_addition_commutative(a, b):
|
||
|
|
assert a + b == b + a
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def user_data():
|
||
|
|
return {"name": "Alice", "age": 30}
|
||
|
|
|
||
|
|
def test_user_creation(user_data):
|
||
|
|
user = User(**user_data)
|
||
|
|
assert user.name == "Alice"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Data Validation (Pydantic)
|
||
|
|
```python
|
||
|
|
from pydantic import BaseModel, Field, validator
|
||
|
|
|
||
|
|
class User(BaseModel):
|
||
|
|
name: str = Field(min_length=1, max_length=100)
|
||
|
|
age: int = Field(ge=0, le=150)
|
||
|
|
email: str
|
||
|
|
|
||
|
|
@validator('email')
|
||
|
|
def email_must_contain_at(cls, v):
|
||
|
|
if '@' not in v:
|
||
|
|
raise ValueError('must contain @')
|
||
|
|
return v
|
||
|
|
```
|
||
|
|
|
||
|
|
## Idioms
|
||
|
|
|
||
|
|
### Comprehensions
|
||
|
|
```python
|
||
|
|
# List comprehension
|
||
|
|
squares = [x**2 for x in range(10) if x % 2 == 0]
|
||
|
|
|
||
|
|
# Dict comprehension
|
||
|
|
word_counts = {word: text.count(word) for word in unique_words}
|
||
|
|
|
||
|
|
# Set comprehension
|
||
|
|
unique_chars = {char for char in text if char.isalpha()}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Context Managers
|
||
|
|
```python
|
||
|
|
# Built-in context managers
|
||
|
|
with open('file.txt', 'r') as f:
|
||
|
|
content = f.read()
|
||
|
|
|
||
|
|
# Custom context manager
|
||
|
|
from contextlib import contextmanager
|
||
|
|
|
||
|
|
@contextmanager
|
||
|
|
def timer():
|
||
|
|
start = time.time()
|
||
|
|
yield
|
||
|
|
print(f"Elapsed: {time.time() - start:.2f}s")
|
||
|
|
```
|
||
|
|
|
||
|
|
### Generators
|
||
|
|
```python
|
||
|
|
def fibonacci():
|
||
|
|
a, b = 0, 1
|
||
|
|
while True:
|
||
|
|
yield a
|
||
|
|
a, b = b, a + b
|
||
|
|
|
||
|
|
def read_lines(file_path):
|
||
|
|
with open(file_path) as f:
|
||
|
|
for line in f:
|
||
|
|
yield line.strip()
|
||
|
|
```
|
||
|
|
|
||
|
|
### F-strings
|
||
|
|
```python
|
||
|
|
name = "Alice"
|
||
|
|
age = 30
|
||
|
|
|
||
|
|
# Basic interpolation
|
||
|
|
msg = f"Name: {name}, Age: {age}"
|
||
|
|
|
||
|
|
# Expression evaluation
|
||
|
|
msg = f"Next year: {age + 1}"
|
||
|
|
|
||
|
|
# Format specs
|
||
|
|
msg = f"Price: ${price:.2f}"
|
||
|
|
msg = f"Hex: {0xFF:X}"
|
||
|
|
```
|
||
|
|
|
||
|
|
## Anti-Patterns
|
||
|
|
|
||
|
|
### Bare Except
|
||
|
|
```python
|
||
|
|
# AVOID: Catches all exceptions including SystemExit
|
||
|
|
try:
|
||
|
|
risky_operation()
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
# USE: Catch specific exceptions
|
||
|
|
try:
|
||
|
|
risky_operation()
|
||
|
|
except ValueError as e:
|
||
|
|
log_error(e)
|
||
|
|
except KeyError as e:
|
||
|
|
log_error(e)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Mutable Defaults
|
||
|
|
```python
|
||
|
|
# AVOID: Default argument created once
|
||
|
|
def append_item(item, items=[]):
|
||
|
|
items.append(item)
|
||
|
|
return items
|
||
|
|
|
||
|
|
# USE: None as sentinel
|
||
|
|
def append_item(item, items=None):
|
||
|
|
if items is None:
|
||
|
|
items = []
|
||
|
|
items.append(item)
|
||
|
|
return items
|
||
|
|
```
|
||
|
|
|
||
|
|
### Global State
|
||
|
|
```python
|
||
|
|
# AVOID: Global mutable state
|
||
|
|
counter = 0
|
||
|
|
|
||
|
|
def increment():
|
||
|
|
global counter
|
||
|
|
counter += 1
|
||
|
|
|
||
|
|
# USE: Class-based state
|
||
|
|
class Counter:
|
||
|
|
def __init__(self):
|
||
|
|
self.count = 0
|
||
|
|
|
||
|
|
def increment(self):
|
||
|
|
self.count += 1
|
||
|
|
```
|
||
|
|
|
||
|
|
### Star Imports
|
||
|
|
```python
|
||
|
|
# AVOID: Pollutes namespace, unclear origins
|
||
|
|
from module import *
|
||
|
|
|
||
|
|
# USE: Explicit imports
|
||
|
|
from module import specific_function, MyClass
|
||
|
|
import module as m
|
||
|
|
```
|
||
|
|
|
||
|
|
## Project Setup
|
||
|
|
|
||
|
|
### pyproject.toml Structure
|
||
|
|
```toml
|
||
|
|
[project]
|
||
|
|
name = "my-project"
|
||
|
|
version = "0.1.0"
|
||
|
|
requires-python = ">=3.11"
|
||
|
|
dependencies = [
|
||
|
|
"pydantic>=2.0",
|
||
|
|
"httpx>=0.25",
|
||
|
|
]
|
||
|
|
|
||
|
|
[project.optional-dependencies]
|
||
|
|
dev = ["pytest", "ruff", "pyright", "hypothesis"]
|
||
|
|
|
||
|
|
[build-system]
|
||
|
|
requires = ["hatchling"]
|
||
|
|
build-backend = "hatchling.build"
|
||
|
|
```
|
||
|
|
|
||
|
|
### src Layout
|
||
|
|
```
|
||
|
|
my-project/
|
||
|
|
├── pyproject.toml
|
||
|
|
└── src/
|
||
|
|
└── my_project/
|
||
|
|
├── __init__.py
|
||
|
|
├── main.py
|
||
|
|
└── utils/
|
||
|
|
├── __init__.py
|
||
|
|
└── helpers.py
|
||
|
|
```
|