安装
uv add --dev jupytext
一次性转换
单独转换
jupytext --to py:percent nbs/01_Preprocess_dataset.ipynb
单独转换并指定输出路径
jupytext --to py:percent nbs/01_Preprocess_dataset.ipynb \
-o scripts/01_preprocess_dataset.py
反向转换
jupytext --to ipynb scripts/01_preprocess_dataset.py \
-o nbs/01_Preprocess_dataset.ipynb
Pair 和 Sync
先 pair:
jupytext --set-formats ipynb,py:percent nbs/01_Preprocess_dataset.ipynb
然后 sync:
jupytext --sync nbs/01_Preprocess_dataset.ipynb
也可以从 .py 文件反向 sync:
jupytext --sync nbs/01_Preprocess_dataset.py
Repo 批量配置
在 repo 根目录放 jupytext.toml:
formats = "ipynb,py:percent"
示例结构:
katlas-raw/
jupytext.toml
nbs/
01_Preprocess_dataset.ipynb
02_Analyze_dataset.ipynb
src/
katlas_raw/
preprocess.py
批量转换和 pair:
find nbs -name "*.ipynb" -not -path "*/.ipynb_checkpoints/*" \
-exec jupytext --set-formats ipynb,py:percent {} \;
从 .py 同步回 .ipynb:
jupytext --sync nbs/01_Preprocess_dataset.py
批量同步:
find nbs -name "*.py" -exec jupytext --sync {} \;
可以变成一个 Skill
Work with the paired percent-format script, not the ipynb JSON.
Target file:
nbs/01_Preprocess_dataset.py
Rules:
- Edit nbs/01_Preprocess_dataset.py only.
- Do not directly edit nbs/01_Preprocess_dataset.ipynb.
- Keep # %% cell boundaries.
- Move reusable logic into src/katlas_raw/preprocess.py when appropriate.
- After editing, run:
jupytext --sync nbs/01_Preprocess_dataset.py
- If possible, validate with:
jupyter nbconvert --execute nbs/01_Preprocess_dataset.ipynb --to notebook --inplace
nbdev 的 .gitignore
如果不想把同步出来的 notebook script 提交到 Git,可以在 nbdev repo 的 .gitignore 里添加:
nbs/*.py
可以放进repo里的 AGENTS.md
## Notebook workflow
- This repo uses Jupytext paired notebooks.
- Treat `nbs/*.py` files in `py:percent` format as the editable source for notebooks.
- Do not directly edit `nbs/*.ipynb` unless explicitly asked.
- Preserve `# %%` cell boundaries when editing notebook scripts.
- After editing a paired notebook script, run:
```bash
jupytext --sync nbs/<notebook_name>.py
```
- Reusable logic should be moved to `src/katlas_raw/*.py`.
- Keep notebooks thin: parameter setup, function calls, quick display, and plots.
- Do not modify files under `data/` unless explicitly asked.
Jupytext Skill
Use Jupytext to work with paired py:percent scripts instead of editing .ipynb JSON directly.
Workflow
-
Check Pairing: If the notebook is paired, edit the
.pypercent-format file. If not paired, ask the user whether to pair only the current notebook or the entire repository. -
Edit Safely: Keep
# %%cell boundaries intact. Do not directly edit the.ipynbfile. -
Refactor: Move reusable logic into the repo package under
src/when appropriate. -
Sync: After editing the paired
.py, sync the changes:Bash
jupytext --sync <target.py> -
Validate: When practical, validate by executing the synced notebook:
Bash
jupyter nbconvert --execute <target.ipynb> --to notebook --inplace
Note: If the repo uses uv, install Jupytext as a dev dependency (uv add --dev jupytext) and prefix commands with uv run jupytext.
Commands & Syncing
One-Off Conversions:
Bash
# Notebook to script
jupytext --to py:percent nbs/01_Preprocess_dataset.ipynb [-o path/to/output.py]
# Script to notebook
jupytext --to ipynb scripts/01_preprocess_dataset.py [-o path/to/output.ipynb]
Pair and Sync:
Bash
# Pair an existing notebook with a py:percent script
jupytext --set-formats ipynb,py:percent nbs/01_Preprocess_dataset.ipynb
# Sync changes from either side
jupytext --sync nbs/01_Preprocess_dataset.ipynb
Repo-Wide Setup
To consistently pair notebooks and scripts across a project, add a jupytext.toml file to the repo root:
Ini, TOML
formats = "ipynb,py:percent"
Batch Operations:
Bash
# Batch pair all notebooks (ignoring checkpoints)
find nbs -name "*.ipynb" -not -path "*/.ipynb_checkpoints/*" -exec jupytext --set-formats ipynb,py:percent {} \;
# Batch sync all scripts
find nbs -name "*.py" -exec jupytext --sync {} \;
Tip: For nbdev repos, consider adding nbs/\*.py to .gitignore if the .py files are only local sync artifacts.
Notebook & Function Style
Use the repo's existing style if consistent. Otherwise, follow these rules:
Notebooks:
- Structure:
# Title(cell 1) > Notebook Goal (cell 2) > Imports > Setup/Config > Main sections (## Section Title) > Reusable defs > Runnable examples. - Rules: Keep code cells focused on one step. Separate imports/defs/examples into different cells. Do not redundantly reload datasets.
Functions:
- Add type annotations for arguments and returns.
- Prefer a one-line docstring explanation if sufficient.
- For multiple/complex arguments, use a multiline signature with inline comments:
Python
def summarize_counts(
df: pd.DataFrame, # source dataframe
group_col: str, # categorical column to count
top_k: int | None = None, # optional limit
) -> pd.DataFrame:
"Returns count of categorical values, optionally limited to top_k."
counts = df[group_col].value_counts().rename_axis(group_col).reset_index(name="count")
return counts.head(top_k) if top_k is not None else counts