{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b41e6db1",
   "metadata": {},
   "source": [
    "# 第 2 天｜工作目录、文件路径、模块引用与软件包\n",
    "\n",
    "**日期：** 2026 年 7 月 21 日  \n",
    "**核心内容：** 工作目录、绝对与相对路径、Jupyter 与 `.py` 的路径差异、`sys.path`、模块导入、pip\n",
    "\n",
    "路径问题是数据分析和编程竞赛中最常见的非语法错误。代码没有写错，但启动位置不同，\n",
    "可能读取了错误文件、找不到 CSV、把结果写到意外目录，或者无法导入本地模块。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27ae2e9b",
   "metadata": {},
   "source": [
    "## 终端命令与 Python 代码\n",
    "\n",
    "| 类型 | 示例 | 输入位置 |\n",
    "|---|---|---|\n",
    "| 终端命令 | `python --version` | 终端 |\n",
    "| 安装命令 | `python -m pip install pandas` | 终端 |\n",
    "| Python 代码 | `import pandas` | `.py` 或 Notebook 代码单元 |\n",
    "| 命令输出 | `Python 3.x.x` | 电脑自动显示，不需要输入 |\n",
    "\n",
    "终端提示符、当前路径和命令执行结果都不应抄进 `.py` 文件。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "18b2ac11",
   "metadata": {},
   "source": [
    "## 一、必须区分的四个位置\n",
    "\n",
    "| 位置 | Python 中的检查方法 | 决定什么 |\n",
    "|---|---|---|\n",
    "| 当前工作目录 `cwd` | `Path.cwd()` | 相对数据路径从哪里开始计算 |\n",
    "| 当前脚本或模块的位置 | `Path(__file__).resolve()` | 当前 `.py` 文件实际存放在哪里 |\n",
    "| 模块搜索路径 | `sys.path` | `import` 会去哪些目录寻找模块 |\n",
    "| Python 解释器的位置 | `sys.executable` | 当前使用哪个环境、去哪个环境找第三方包 |\n",
    "\n",
    "这四个位置可能相同，也可能完全不同。最关键的两条规则是：\n",
    "\n",
    "1. `open(\"data/a.csv\")`、`pd.read_csv(\"data/a.csv\")` 等相对文件路径，默认从 **cwd** 开始；\n",
    "2. `import helper` 不是按照 cwd 单独判断，而是依次搜索 **`sys.path`** 中的目录。\n",
    "\n",
    "**相对文件路径并不自动相对于当前 `.py` 文件。** 这是本节最重要的结论。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "03ce8692",
   "metadata": {},
   "source": [
    "## 二、检查当前工作目录\n",
    "\n",
    "在 PowerShell 中：\n",
    "\n",
    "```powershell\n",
    "Get-Location\n",
    "Get-ChildItem\n",
    "Set-Location \"D:\\Python暑期训练\\第01周\"\n",
    "```\n",
    "\n",
    "在 CMD 中：\n",
    "\n",
    "```bat\n",
    "cd\n",
    "dir\n",
    "cd /d \"D:\\Python暑期训练\\第01周\"\n",
    "```\n",
    "\n",
    "在 Python 或 Notebook 中：\n",
    "\n",
    "```python\n",
    "from pathlib import Path\n",
    "\n",
    "print(Path.cwd())\n",
    "```\n",
    "\n",
    "`cwd` 是进程的“出发位置”。通过终端启动程序时，它通常继承终端当前所在的目录；\n",
    "通过编辑器或 Notebook 启动时，则由编辑器、工作区和内核启动方式共同决定。\n",
    "因此不要凭感觉判断，运行前直接打印 `Path.cwd()`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dc8561a5",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "import sys\n",
    "\n",
    "print(\"当前工作目录 cwd：\", Path.cwd())\n",
    "print(\"Python 解释器：\", sys.executable)\n",
    "print(\"sys.path[0]：\", sys.path[0])\n",
    "\n",
    "if \"__file__\" in globals():\n",
    "    print(\"当前脚本文件：\", Path(__file__).resolve())\n",
    "else:\n",
    "    print(\"__file__：Notebook 中通常没有这个变量\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "894e3cf9",
   "metadata": {},
   "source": [
    "## 三、绝对路径与相对路径\n",
    "\n",
    "假设项目结构为：\n",
    "\n",
    "```text\n",
    "D:\\Python暑期训练\\路径演示\n",
    "├── data\n",
    "│   └── sales.csv\n",
    "├── notebooks\n",
    "│   └── analysis.ipynb\n",
    "├── scripts\n",
    "│   └── main.py\n",
    "├── src\n",
    "│   ├── __init__.py\n",
    "│   └── cleaner.py\n",
    "└── outputs\n",
    "```\n",
    "\n",
    "| 写法 | 类型 | 实际含义 |\n",
    "|---|---|---|\n",
    "| `D:\\Python暑期训练\\路径演示\\data\\sales.csv` | 绝对路径 | 从盘符开始，位置完整 |\n",
    "| `data\\sales.csv` | 相对路径 | `cwd/data/sales.csv` |\n",
    "| `..\\data\\sales.csv` | 相对路径 | cwd 的上一级目录中的 `data/sales.csv` |\n",
    "| `.\\data\\sales.csv` | 相对路径 | 与 `data/sales.csv` 等价 |\n",
    "\n",
    "相对路径更便于把整个项目复制到另一台电脑，但必须统一工作目录；绝对路径定位明确，\n",
    "却会把盘符和用户名写死，不适合提交给他人。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d831c6c0",
   "metadata": {},
   "source": [
    "## 四、Windows 路径字符串的正确写法\n",
    "\n",
    "反斜杠在 Python 字符串中可能是转义符。例如 `\\n` 表示换行，`\\t` 表示制表符。\n",
    "\n",
    "推荐优先使用 `pathlib.Path`：\n",
    "\n",
    "```python\n",
    "from pathlib import Path\n",
    "\n",
    "data_file = Path(\"D:/Python暑期训练/路径演示/data/sales.csv\")\n",
    "```\n",
    "\n",
    "也可以使用原始字符串：\n",
    "\n",
    "```python\n",
    "data_file = Path(r\"D:\\Python暑期训练\\路径演示\\data\\sales.csv\")\n",
    "```\n",
    "\n",
    "组合路径不要手工反复拼接斜杠：\n",
    "\n",
    "```python\n",
    "project_root = Path(\"D:/Python暑期训练/路径演示\")\n",
    "data_file = project_root / \"data\" / \"sales.csv\"\n",
    "```\n",
    "\n",
    "`Path` 会根据操作系统生成合适的路径，代码也更容易阅读。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26b56a08",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "relative_file = Path(\"data\") / \"sales.csv\"\n",
    "print(\"代码中写的相对路径：\", relative_file)\n",
    "print(\"按当前 cwd 解析后：\", relative_file.resolve())\n",
    "print(\"文件是否存在：\", relative_file.exists())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf42fe0b",
   "metadata": {},
   "source": [
    "## 五、直接运行 `.py` 文件时路径怎样变化\n",
    "\n",
    "假设 `main.py` 位于：\n",
    "\n",
    "```text\n",
    "D:\\Python暑期训练\\路径演示\\scripts\\main.py\n",
    "```\n",
    "\n",
    "### 情况 A：先进入项目根目录再运行\n",
    "\n",
    "```powershell\n",
    "cd \"D:\\Python暑期训练\\路径演示\"\n",
    "py scripts\\main.py\n",
    "```\n",
    "\n",
    "此时通常：\n",
    "\n",
    "```text\n",
    "cwd                         D:\\Python暑期训练\\路径演示\n",
    "__file__                    D:\\Python暑期训练\\路径演示\\scripts\\main.py\n",
    "Path(\"data/sales.csv\")      D:\\Python暑期训练\\路径演示\\data\\sales.csv\n",
    "sys.path[0]                 D:\\Python暑期训练\\路径演示\\scripts\n",
    "```\n",
    "\n",
    "### 情况 B：先进入 scripts 再运行\n",
    "\n",
    "```powershell\n",
    "cd \"D:\\Python暑期训练\\路径演示\\scripts\"\n",
    "py main.py\n",
    "```\n",
    "\n",
    "此时 `cwd` 变成 `scripts`，所以 `Path(\"data/sales.csv\")` 会指向\n",
    "`scripts/data/sales.csv`，通常会报 `FileNotFoundError`。\n",
    "\n",
    "### 情况 C：在其他目录使用完整路径运行\n",
    "\n",
    "```powershell\n",
    "cd \"C:\\Users\\student\"\n",
    "py \"D:\\Python暑期训练\\路径演示\\scripts\\main.py\"\n",
    "```\n",
    "\n",
    "Python 找到了脚本，但 `cwd` 仍然是 `C:\\Users\\student`。因此脚本路径正确，\n",
    "并不代表脚本中的相对数据路径也正确。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7466fc78",
   "metadata": {},
   "source": [
    "## 六、VS Code 运行 `.py` 时的工作目录\n",
    "\n",
    "点击 **Run Python File in Terminal** 时：\n",
    "\n",
    "- 脚本文件位置由当前编辑器标签决定；\n",
    "- `cwd` 可能是打开的工作区根目录，也可能继承已经打开的终端目录；\n",
    "- VS Code 设置和终端之前执行过的 `cd` 都可能影响结果。\n",
    "\n",
    "因此程序开头可暂时加入：\n",
    "\n",
    "```python\n",
    "from pathlib import Path\n",
    "import sys\n",
    "\n",
    "print(\"cwd =\", Path.cwd())\n",
    "print(\"__file__ =\", Path(__file__).resolve())\n",
    "print(\"python =\", sys.executable)\n",
    "```\n",
    "\n",
    "使用 VS Code 时应当通过“文件 → 打开文件夹”打开**整个项目根目录**，不要只双击打开一个孤立的\n",
    "`.py` 文件。运行前查看终端提示符，确认它当前位于哪里。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8650e77d",
   "metadata": {},
   "source": [
    "## 七、Jupyter Notebook 中的工作目录\n",
    "\n",
    "Jupyter 内核的 `cwd` 通常是 Notebook 所在目录或内核启动目录；Classic Notebook、\n",
    "JupyterLab、VS Code Notebook 和不同设置可能表现不同。唯一可靠的方法是现场检查：\n",
    "\n",
    "```python\n",
    "from pathlib import Path\n",
    "\n",
    "print(Path.cwd())\n",
    "```\n",
    "\n",
    "也可以使用 Jupyter 魔法命令：\n",
    "\n",
    "```python\n",
    "%pwd\n",
    "%cd ..\n",
    "```\n",
    "\n",
    "需要特别注意：\n",
    "\n",
    "- Notebook 通常没有 `__file__`，不能直接复制脚本中的 `Path(__file__)` 写法；\n",
    "- `%cd` 会改变当前内核后续所有单元格的 cwd；\n",
    "- `!dir` 可以查看目录；\n",
    "- `!cd ..` 在临时子进程中执行，通常不会持续改变 Python 内核的 cwd；\n",
    "- 单元格执行顺序会保留状态，前面执行过的 `%cd` 会影响后面读取文件。\n",
    "\n",
    "假设 `analysis.ipynb` 的 cwd 为 `路径演示/notebooks`：\n",
    "\n",
    "```python\n",
    "# 错误目标：notebooks/data/sales.csv\n",
    "pd.read_csv(\"data/sales.csv\")\n",
    "\n",
    "# 正确目标：项目根目录下的 data/sales.csv\n",
    "pd.read_csv(\"../data/sales.csv\")\n",
    "```\n",
    "\n",
    "但 `..` 是否正确仍取决于实际 cwd，所以读取前应先检查解析后的完整路径。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a58b17d4",
   "metadata": {},
   "source": [
    "## 八、读取文件前的标准检查\n",
    "\n",
    "以 pandas 读取 CSV 为例：\n",
    "\n",
    "```python\n",
    "from pathlib import Path\n",
    "import pandas as pd\n",
    "\n",
    "data_file = Path(\"data\") / \"sales.csv\"\n",
    "\n",
    "print(\"cwd：\", Path.cwd())\n",
    "print(\"准备读取：\", data_file.resolve())\n",
    "print(\"是否存在：\", data_file.exists())\n",
    "\n",
    "if not data_file.exists():\n",
    "    raise FileNotFoundError(f\"找不到文件：{data_file.resolve()}\")\n",
    "\n",
    "df = pd.read_csv(data_file, encoding=\"utf-8-sig\")\n",
    "print(df.head())\n",
    "```\n",
    "\n",
    "同一规则适用于：\n",
    "\n",
    "```python\n",
    "pd.read_excel(\"data/orders.xlsx\")\n",
    "open(\"config.json\", encoding=\"utf-8\")\n",
    "Path(\"notes.txt\").read_text(encoding=\"utf-8\")\n",
    "```\n",
    "\n",
    "写文件也从 cwd 解析。输出目录不存在时要先创建：\n",
    "\n",
    "```python\n",
    "output_dir = Path(\"outputs\")\n",
    "output_dir.mkdir(parents=True, exist_ok=True)\n",
    "df.to_csv(output_dir / \"result.csv\", index=False, encoding=\"utf-8-sig\")\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3978e5d7",
   "metadata": {},
   "source": [
    "## 九、让 `.py` 文件稳定定位项目数据\n",
    "\n",
    "如果脚本必须在任意目录都能运行，可以基于脚本自己的位置定位项目根目录。\n",
    "假设脚本位于项目的 `scripts` 子目录：\n",
    "\n",
    "```python\n",
    "from pathlib import Path\n",
    "\n",
    "SCRIPT_FILE = Path(__file__).resolve()\n",
    "SCRIPT_DIR = SCRIPT_FILE.parent\n",
    "PROJECT_ROOT = SCRIPT_DIR.parent\n",
    "\n",
    "DATA_FILE = PROJECT_ROOT / \"data\" / \"sales.csv\"\n",
    "OUTPUT_DIR = PROJECT_ROOT / \"outputs\"\n",
    "\n",
    "print(\"脚本：\", SCRIPT_FILE)\n",
    "print(\"项目根目录：\", PROJECT_ROOT)\n",
    "print(\"数据文件：\", DATA_FILE)\n",
    "```\n",
    "\n",
    "| 表达式 | 结果 |\n",
    "|---|---|\n",
    "| `Path(__file__)` | 当前 `.py` 文件路径 |\n",
    "| `.resolve()` | 转成规范化的绝对路径 |\n",
    "| `.parent` | 上一级目录 |\n",
    "| `.parents[1]` | 上两级目录 |\n",
    "\n",
    "在被导入的库文件中，`__file__` 指向的是**该库文件自身**，不是调用它的主程序。\n",
    "这适合定位库自带的模板、配置等资源，但不应假定它等于用户的数据目录。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fdf8da1e",
   "metadata": {},
   "source": [
    "## 十、Notebook 中稳定定位项目根目录\n",
    "\n",
    "Notebook 没有可靠的 `__file__`，通常先以 `Path.cwd()` 为起点。若 Notebook 固定放在\n",
    "`notebooks` 子目录，可明确写出：\n",
    "\n",
    "```python\n",
    "from pathlib import Path\n",
    "\n",
    "NOTEBOOK_DIR = Path.cwd().resolve()\n",
    "PROJECT_ROOT = NOTEBOOK_DIR.parent\n",
    "DATA_FILE = PROJECT_ROOT / \"data\" / \"sales.csv\"\n",
    "\n",
    "print(\"Notebook cwd：\", NOTEBOOK_DIR)\n",
    "print(\"项目根目录：\", PROJECT_ROOT)\n",
    "print(\"数据文件：\", DATA_FILE)\n",
    "```\n",
    "\n",
    "更稳妥的项目规范是：\n",
    "\n",
    "1. 固定 `data`、`notebooks`、`scripts`、`src`、`outputs` 的层级；\n",
    "2. Notebook 开头只定义一次 `PROJECT_ROOT`；\n",
    "3. 后面所有文件路径都由 `PROJECT_ROOT / ...` 组合；\n",
    "4. 不在多个单元格中随意使用 `%cd` 或 `os.chdir()`。\n",
    "\n",
    "如果项目目录层级变化，只需修改根目录定义，不需要逐行修改所有读取代码。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70b75884",
   "metadata": {},
   "source": [
    "## 十一、数据文件路径与模块引用路径不是一回事\n",
    "\n",
    "假设 `scripts/main.py` 中包含：\n",
    "\n",
    "```python\n",
    "from src.cleaner import clean_sales\n",
    "df = pd.read_csv(\"data/sales.csv\")\n",
    "```\n",
    "\n",
    "两行代码使用两套机制：\n",
    "\n",
    "| 代码 | 搜索依据 |\n",
    "|---|---|\n",
    "| `pd.read_csv(\"data/sales.csv\")` | `cwd` |\n",
    "| `from src.cleaner import ...` | `sys.path` |\n",
    "\n",
    "查看模块搜索路径：\n",
    "\n",
    "```python\n",
    "import sys\n",
    "\n",
    "for item in sys.path:\n",
    "    print(item)\n",
    "```\n",
    "\n",
    "查看一个已经导入的库来自哪里：\n",
    "\n",
    "```python\n",
    "import pandas\n",
    "\n",
    "print(pandas.__file__)\n",
    "```\n",
    "\n",
    "`site-packages` 中的第三方库通常可以从任何项目导入；自己写的 `src/cleaner.py` 只有在项目根目录\n",
    "位于 `sys.path` 中，或项目已经被安装成软件包时才能稳定导入。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "300626c3",
   "metadata": {},
   "source": [
    "## 十二、直接运行脚本与 `python -m` 的区别\n",
    "\n",
    "在项目根目录执行：\n",
    "\n",
    "```powershell\n",
    "py scripts\\main.py\n",
    "```\n",
    "\n",
    "Python 把 `scripts` 目录放在模块搜索路径前部，`main.py` 被当作独立脚本运行。\n",
    "此时包内相对导入可能报：\n",
    "\n",
    "```text\n",
    "ImportError: attempted relative import with no known parent package\n",
    "```\n",
    "\n",
    "如果 `scripts` 是带 `__init__.py` 的包，可以在项目根目录使用：\n",
    "\n",
    "```powershell\n",
    "py -m scripts.main\n",
    "```\n",
    "\n",
    "`-m` 表示按模块运行：\n",
    "\n",
    "- 当前工作目录仍是项目根目录；\n",
    "- 项目根目录进入模块搜索路径；\n",
    "- Python 知道 `main` 属于 `scripts` 包；\n",
    "- `from .helper import ...` 这类包内相对导入可以正常解析。\n",
    "\n",
    "`from .helper import x` 中的点表示“相对于当前 Python 包”，不是相对于 cwd，也不是普通文件路径的 `.`。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ecbee8a",
   "metadata": {},
   "source": [
    "## 十三、在 Jupyter 中引用本地 `.py` 模块\n",
    "\n",
    "如果 Notebook 的 cwd 是 `notebooks`，而模块位于项目根目录的 `src` 中，直接\n",
    "`from src.cleaner import ...` 可能失败，因为项目根目录不在 `sys.path`。\n",
    "\n",
    "初学阶段可明确加入项目根目录：\n",
    "\n",
    "```python\n",
    "from pathlib import Path\n",
    "import sys\n",
    "\n",
    "PROJECT_ROOT = Path.cwd().resolve().parent\n",
    "\n",
    "if str(PROJECT_ROOT) not in sys.path:\n",
    "    sys.path.insert(0, str(PROJECT_ROOT))\n",
    "\n",
    "from src.cleaner import clean_sales\n",
    "```\n",
    "\n",
    "这是一种过渡性做法。正式项目更推荐把本地代码组织成包，再使用\n",
    "`py -m pip install -e .` 进行可编辑安装。\n",
    "\n",
    "在 Notebook 中运行脚本还可使用：\n",
    "\n",
    "```python\n",
    "%run ../scripts/main.py\n",
    "```\n",
    "\n",
    "`%run` 找到了脚本，不代表 cwd 自动切换到脚本目录；脚本中的相对数据路径仍可能按 Notebook\n",
    "当前 cwd 解析。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f8a69c76",
   "metadata": {},
   "source": [
    "## 十四、典型路径错误与诊断顺序\n",
    "\n",
    "| 报错或现象 | 重点检查 |\n",
    "|---|---|\n",
    "| `FileNotFoundError` | `Path.cwd()`、目标路径 `.resolve()`、`.exists()` |\n",
    "| 读取了旧文件 | 实际解析路径、同名文件、终端所在目录 |\n",
    "| 输出文件找不到 | cwd、输出目录是否提前创建 |\n",
    "| `No module named src` | `sys.path` 是否包含项目根目录 |\n",
    "| 包内相对导入失败 | 是否直接运行了包内文件，是否应改用 `py -m` |\n",
    "| Notebook 能运行、脚本不能 | Notebook 曾修改 cwd/sys.path 或保留了旧变量 |\n",
    "| 脚本能运行、Notebook 不能 | Notebook cwd、内核解释器、项目根目录是否在 sys.path |\n",
    "\n",
    "路径报错时固定打印：\n",
    "\n",
    "```python\n",
    "from pathlib import Path\n",
    "import sys\n",
    "\n",
    "print(\"cwd =\", Path.cwd())\n",
    "print(\"python =\", sys.executable)\n",
    "print(\"sys.path[0] =\", sys.path[0])\n",
    "print(\"target =\", Path(\"data/sales.csv\").resolve())\n",
    "print(\"exists =\", Path(\"data/sales.csv\").exists())\n",
    "```\n",
    "\n",
    "在 `.py` 中再增加 `print(\"__file__ =\", Path(__file__).resolve())`。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c711bc19",
   "metadata": {},
   "source": [
    "## 十五、Python 与 pip 版本检查\n",
    "\n",
    "Windows 常用命令：\n",
    "\n",
    "```text\n",
    "py --version\n",
    "py -m pip --version\n",
    "```\n",
    "\n",
    "如果电脑不支持 `py`，使用：\n",
    "\n",
    "```text\n",
    "python --version\n",
    "python -m pip --version\n",
    "```\n",
    "\n",
    "`python -m pip` 的含义是：让当前这个 Python 启动它自己的 pip。这样能减少“包装在 A 环境，代码却由 B 环境运行”的问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0e2d24a",
   "metadata": {},
   "source": [
    "## 十六、第一阶段需要的软件包\n",
    "\n",
    "```text\n",
    "py -m pip install pandas openpyxl notebook ipykernel\n",
    "```\n",
    "\n",
    "| 软件包 | 用途 |\n",
    "|---|---|\n",
    "| `pandas` | 表格数据清洗、计算、分组和导出 |\n",
    "| `openpyxl` | 支持 pandas 读写 `.xlsx` |\n",
    "| `notebook`、`ipykernel` | 运行 Notebook 课件和 Python 内核 |\n",
    "\n",
    "网页采集阶段还会使用：\n",
    "\n",
    "```text\n",
    "py -m pip install requests beautifulsoup4 lxml\n",
    "```\n",
    "\n",
    "在 Jupyter Notebook 中需要临时安装包时优先使用：\n",
    "\n",
    "```python\n",
    "%pip install pandas\n",
    "```\n",
    "\n",
    "`%pip` 会针对当前 Notebook 内核安装。安装完成后如仍无法导入，重启内核再运行。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c96eb26",
   "metadata": {},
   "source": [
    "## 十七、安装、导入与文件路径的区别\n",
    "\n",
    "```text\n",
    "pip install pandas\n",
    "```\n",
    "\n",
    "是把 pandas 安装进某个 Python 环境。\n",
    "\n",
    "```python\n",
    "import pandas\n",
    "```\n",
    "\n",
    "是在当前程序中使用已经安装的 pandas。\n",
    "\n",
    "安装通常只需做一次；每个需要使用 pandas 的程序都要写 `import`。\n",
    "\n",
    "三者不要混淆：\n",
    "\n",
    "| 操作 | 主要依据 |\n",
    "|---|---|\n",
    "| `py -m pip install pandas` | `py` 指向的 Python 环境 |\n",
    "| `import pandas` | 当前解释器的 `sys.path` |\n",
    "| `pd.read_csv(\"data/a.csv\")` | 当前进程的 cwd |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e59b350",
   "metadata": {},
   "source": [
    "## 十八、软件包及安装位置检查"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7f9ef819",
   "metadata": {},
   "outputs": [],
   "source": [
    "from importlib import import_module\n",
    "import sys\n",
    "\n",
    "print(\"当前内核 Python：\", sys.executable)\n",
    "\n",
    "for package_name in [\"pandas\", \"openpyxl\"]:\n",
    "    try:\n",
    "        package = import_module(package_name)\n",
    "        print(f\"{package_name} 版本：\", package.__version__)\n",
    "        print(f\"{package_name} 路径：\", package.__file__)\n",
    "    except ModuleNotFoundError:\n",
    "        print(f\"{package_name} 未安装在当前内核中\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75f9199f",
   "metadata": {},
   "source": [
    "## 代码解读\n",
    "\n",
    "| 代码 | 含义 |\n",
    "|---|---|\n",
    "| `sys.executable` | 查看当前 Notebook 内核的 Python 路径 |\n",
    "| `import_module(package_name)` | 根据字符串动态导入指定模块 |\n",
    "| `package.__version__` | 读取当前导入模块的版本字符串 |\n",
    "| `package.__file__` | 查看当前导入模块的实际文件位置 |\n",
    "| `except ModuleNotFoundError` | 缺包时输出诊断信息，避免整份课件中断 |\n",
    "\n",
    "常见别名写法：\n",
    "\n",
    "```python\n",
    "import pandas as pd\n",
    "```\n",
    "\n",
    "后续即可用 `pd.DataFrame()`，这是三套真题中的标准写法。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "679d4c56",
   "metadata": {},
   "source": [
    "## 十九、环境与路径联合排错\n",
    "\n",
    "| 报错或现象 | 常见原因 | 检查顺序 |\n",
    "|---|---|---|\n",
    "| `No module named pandas` | 当前解释器没有安装 pandas | 看 VS Code 解释器，再用该 Python 安装 |\n",
    "| `python 不是内部或外部命令` | PATH 或命令名问题 | 尝试 `py`，重开终端 |\n",
    "| `Permission denied` | 账号没有安装权限 | 保存完整报错，使用管理员或用户安装 |\n",
    "| 下载超时 | 网络或镜像问题 | 重试一次，再使用离线包 |\n",
    "| Notebook 内核与终端不同 | 选了不同环境 | 检查 Notebook 右上角内核 |\n",
    "\n",
    "排错时应记录：运行命令、cwd、脚本路径、目标文件解析路径、Python 路径、pip 路径和完整报错最后一行。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12343113",
   "metadata": {},
   "source": [
    "## 二十、与真题的关系\n",
    "\n",
    "- 名仕题：`requests`、`json`、`pandas`、`openpyxl`\n",
    "- 科图题：`requests`、`beautifulsoup4`、`pandas`、`lxml`\n",
    "- 青娅题：`requests`、`beautifulsoup4`、`pandas`、`lxml`\n",
    "\n",
    "三套真题既会导入第三方库，也会读取页面数据、保存 CSV/Excel。Excel 导出失败时，\n",
    "不一定是 `to_excel()` 写错，也可能是缺少 `openpyxl`、输出目录不存在或 cwd 与预期不同。\n",
    "\n",
    "## 针对性练习\n",
    "\n",
    "使用三个不同目录运行 `学生练习.py`，比较 cwd、`__file__`、`sys.path[0]`、\n",
    "相对路径和脚本锚定路径。然后把诊断代码复制到 Notebook，观察 `__file__` 和工作目录的变化。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3"
  },
  "title": "第 2 天｜工作目录、文件路径、模块引用与软件包"
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
