
本文详解如何在 Databricks 中安全、高效地跨笔记本复用自定义类(如 gpg_encryption),重点对比 %run 与 dbutils.notebook.run() 的适用场景,指出 dbutils.notebook.exit() 无法传递 Python 类对象的根本原因,并提供可落地的参数化初始化 + %run 模块化方案。
本文详解如何在 databricks 中安全、高效地跨笔记本复用自定义类(如 `gpg_encryption`),重点对比 `%run` 与 `dbutils.notebook.run()` 的适用场景,指出 `dbutils.notebook.exit()` 无法传递 python 类对象的根本原因,并提供可落地的参数化初始化 + `%run` 模块化方案。
在 Azure Databricks 中实现笔记本间的代码复用,尤其是共享自定义类(如用于 GPG 加密的 gpg_encryption),是构建可维护数据管道的关键能力。但实践中常因混淆两种调用机制——%run(内联执行)与 dbutils.notebook.run()(独立作业)——导致变量不可见、类无法实例化等错误。你遇到的 NameError: name 'gpg_encryption' is not defined 正是典型误区:dbutils.notebook.run() 启动的是一个完全隔离的新 Spark 作业会话,其内存空间与父笔记本(notebook1)物理隔离;dbutils.notebook.exit() 仅支持返回字符串(str),无法序列化并传递 Python 类、函数或任意对象。
✅ 正确做法:优先使用 %run 实现类定义复用
当目标是“在 notebook1 中直接使用 notebook2 定义的类”时,%run 是唯一符合语义的方案。它将目标笔记本内容内联注入当前会话作用域,使其中定义的类、函数、变量立即可用。
示例:安全复用 gpg_encryption 类
假设 notebook2(路径:./shared/gpg_utils.py 或 ./shared/gpg_utils.ipynb)内容如下:
# notebook2: ./shared/gpg_utils.ipynb
import os
import gnupg
from pyspark.sql import SparkSession
class gpg_encryption(gnupg.GPG):
"""
扩展 GnuPG 库,适配 Databricks 环境。
支持 BASE64 密钥导入、CSV 解密为 Pandas DataFrame。
"""
def __init__(self, catalog: str, storage_account: str):
super().__init__()
self.gpg.encoding = 'utf-8'
self.catalog = catalog
self.storage_account = storage_account
self.asc_key_path = f'/Volumes/{self.catalog}/bronze/private'
self._create_key_volume()
def _create_key_volume(self):
"""在 Databricks Unity Catalog 中创建密钥存储 Volume"""
spark = SparkSession.getActiveSession()
if spark is None:
raise RuntimeError("No active Spark session found.")
spark.sql(f"""
CREATE EXTERNAL VOLUME IF NOT EXISTS {self.catalog}.bronze.private
LOCATION 'abfss://bronze@{self.storage_account}.dfs.core.windows.net/private'
""")
在 notebook1 中,单独一行执行:
%run ./shared/gpg_utils.ipynb
✅ 此时 gpg_encryption 类已加载至 notebook1 的全局命名空间,可直接实例化:
# notebook1 CATALOG = "lakehouse_dev" STORAGEACCOUNT_NAME = "mystorageaccount" # 实例化类(参数在运行时传入) gpg_handler = gpg_encryption(catalog=CATALOG, storage_account=STORAGEACCOUNT_NAME) # 后续调用方法 # gpg_handler.decrypt_csv(...)
⚠️ 关键注意事项:
- %run 必须独占一个单元格(cell),不能与其他代码混写;
- 路径支持相对(./shared/...)和绝对(/Users/you@org.com/shared/...)格式;
- 若 notebook2 依赖外部库(如 gnupg),需确保集群已安装(可通过集群库管理或 %pip install gnupg);
- 不支持向 %run 目标笔记本传递参数(如小工具值需提前在 notebook2 中定义默认值)。
❌ 为何 dbutils.notebook.run() + exit() 不适用此场景?
你尝试的方案:
# notebook1
result = dbutils.notebook.run("./notebook2", 60, {"CATALOG": CATALOG})
# notebook2 中:dbutils.notebook.exit(gpg_encryption) ← 错误!
存在两个根本性限制:
-
类型限制:dbutils.notebook.exit(value) 的 value 必须是字符串。传入类对象会触发 TypeError,即使强制 str(gpg_encryption),返回的也只是
字符串,无法在 notebook1 中还原为可调用类。 - 会话隔离:notebook2 在新作业中运行,其 __init__ 构造、Volume 创建等操作均发生在独立 Spark 上下文中,对 notebook1 的会话无任何影响。
? 提示:dbutils.notebook.run() 的正确定位是 工作流编排(如按文件列表循环调用解密任务),而非代码模块化。它适合返回结构化结果(如 JSON 字符串 "{'status': 'success', 'rows_processed': 123}"),供父笔记本做条件判断(if result == "success": ...)。
? 推荐进阶方案:工作区文件 + import
对于生产环境,更推荐将核心逻辑封装为 工作区中的 .py 文件(非 .ipynb),再通过标准 import 引入:
在工作区创建 /Workspace/Users/you@org.com/lib/gpg_utils.py
内容同上(含 __init__ 参数化)
-
在 notebook1 中:
import sys sys.path.append("/Workspace/Users/you@org.com/lib") # 确保路径可达 from gpg_utils import gpg_encryption gpg_handler = gpg_encryption(catalog=CATALOG, storage_account=STORAGEACCOUNT_NAME)
该方式支持 IDE 调试、Git 版本控制、单元测试,是 Databricks 官方推荐的代码模块化首选方法。
✅ 总结:选对工具,事半功倍
| 场景 | 推荐方法 | 关键优势 | 注意事项 |
|---|---|---|---|
| 快速原型、轻量复用类/函数 | %run notebook_path | 零配置、即时生效、作用域共享 | 无参数传递、无版本控制、易耦合 |
| 生产级模块化、团队协作 | 工作区 .py 文件 + import | 支持 Git、IDE、测试、清晰依赖 | 需管理 sys.path 或安装为集群库 |
| 动态工作流编排(如循环处理文件) | dbutils.notebook.run() | 支持参数传入、返回字符串、作业级隔离 | 无法传递对象、开销大、不适用于类复用 |
请始终牢记:%run 是模块化,dbutils.notebook.run() 是编排。将类定义放在 notebook2 并期望 notebook1 直接调用,本质是模块化需求——请坚定选择 %run 或 .py 文件方案。











