在 jupyter 笔记本中,如果我定义一个类,实例化它并使用 joblib 保存对象,我可以将它加载回来:
import joblib
class Duck():
def quack(self):
print("Quack!")
my_duck = Duck()
joblib.dump(my_duck, "my_duck.joblib")
loaded_duck = joblib.load("my_duck.joblib")
loaded_duck.quack()
输出:
Quack!
但是如果我尝试加载new笔记本(甚至是常规的.py脚本),我将无法:
import joblib
loaded_duck = joblib.load("my_duck.joblib")
loaded_duck.quack()
输出:
AttributeError: module '__main__' has no attribute 'Duck'
如何解决这个问题?
尽管这个问题很旧并且现有的答案是正确的,但我想扩大讨论,因为我花了很多时间来理解这个问题。
joblib
是一个类似于 pickle
的允许对象持久化的包。这些对象是类的实例,例如通常,数据帧是 pandas.DataFrame
类的实例。如果序列化该对象,即 joblib.dump(“my_df.joblib”)
,该对象将存储在一个二进制文件中,该文件由其类名标记。
如果您反序列化文件以取回对象,即
joblib.load(“my_df.joblib”)
,Python 必须搜索类定义才能实例化它。为了跟上我们的数据框示例,这将对应于 pandas.DataFrame
。因此,如果在当前上下文(不同的脚本、不同的笔记本等)中,您没有安装 pandas
,您将得到著名的 ModuleNotFoundError
,因为 Python 不知道如何实例化您的数据框。
现在您必须将这种直觉转移到您的自定义类中:如果您创建它时它的定义位于主模块中,那么在您加载它时,相同的定义也必须位于主模块中。在您的情况下,这是
__main__.Duck
,这意味着您需要将类定义复制粘贴到新笔记本中。然而,这不是一个非常实用的方法。所以我建议你创建一个额外的模块,例如名为 utils
或类似的文件夹,您可以在其中放置包含自定义类的所有脚本。结构可能如下所示:
yourProject/
│
├── notebook.ipynb
├── utils/
| ├── __init__.py
| └── animals.py
|
└── my_duck.joblib
并且在
animals.py
内:
class Duck():
def quack(self):
print("Quack!")
如果您现在导入自定义类,您将使用:
from utils.animals import Duck
像这样,
joblib
相应地标记对象,当您在其他地方加载对象时,您可以只使用自定义模块(例如复制粘贴 utils 文件夹)。只需确保相对路径与导入 ./utils/animals.py
时 Python 在 Duck
中搜索的路径完全相同即可。
编辑: 遵循上述逻辑,最优雅的解决方案是创建您自己的 python 包,其中包含所有自定义类/方法,然后在需要的地方
import
它。
不知道你是否还像1个月前一样遇到这个问题;但是,如果其他人也遇到同样的问题:
您收到该错误是因为,在新笔记本(您在其中导入对象)中,您没有导入类的定义。
首先尝试在新笔记本中导入类
Duck
:
from *script_duck import Duck
import joblib
loaded_duck = joblib.load("my_duck.joblib")
loaded_duck.quack()