在Python中,类名的自动完全限定是如何工作的?[与物体酸洗相关]
(可以直接跳到问题,再往下跳,跳过介绍。) 从用户定义的类中提取Python对象有一个常见的困难:在Python中,类名的自动完全限定是如何工作的?[与物体酸洗相关],python,class,import,pickle,fully-qualified-naming,Python,Class,Import,Pickle,Fully Qualified Naming,(可以直接跳到问题,再往下跳,跳过介绍。) 从用户定义的类中提取Python对象有一个常见的困难: # This is program dumper.py import pickle class C(object): pass with open('obj.pickle', 'wb') as f: pickle.dump(C(), f) 事实上,使用 # This is program loader.py with open('obj.pickle', 'rb') as f
# This is program dumper.py
import pickle
class C(object):
pass
with open('obj.pickle', 'wb') as f:
pickle.dump(C(), f)
事实上,使用
# This is program loader.py
with open('obj.pickle', 'rb') as f:
obj = pickle.load(f)
导致
AttributeError: 'module' object has no attribute 'C'
事实上,类是按名称(“C”)进行pickle的,loader.py
程序对C
一无所知。常见的解决方案包括使用导入
from dumper import C # Objects of class C can be imported
with open('obj.pickle', 'rb') as f:
obj = pickle.load(f)
但是,该解决方案有一些缺点,包括必须导入pickle对象引用的所有类(可能有很多);此外,本地名称空间会被dumper.py
程序中的名称污染
现在,解决方案包括在酸洗之前完全限定对象:
# New dumper.py program:
import pickle
import dumper # This is this very program!
class C(object):
pass
with open('obj.pickle', 'wb') as f:
pickle.dump(dumper.C(), f) # Fully qualified class
使用上面的原始loader.py
程序取消勾选现在可以直接工作(无需从转储程序导入C执行)
问题:现在,dumper.py
中的其他类似乎在酸洗时自动完全合格,我想知道这是如何工作的,以及这是否是一种可靠的、有文档记录的行为:
import pickle
import dumper # This is this very program!
class D(object): # New class!
pass
class C(object):
def __init__(self):
self.d = D() # *NOT* fully qualified
with open('obj.pickle', 'wb') as f:
pickle.dump(dumper.C(), f) # Fully qualified pickle class
现在,使用原始的loader.py
程序取消勾选也可以工作(无需从转储程序导入C执行)print obj.d
提供了一个完全限定的类,这让我感到惊讶:
<dumper.D object at 0x122e130>
这种行为非常方便,因为只有顶部的pickle对象必须使用模块名(dumper.C()
)完全限定。但这种行为可靠吗?为什么类是按名称(“D”)进行pickle的,但是取消pickle决定pickleself.D
属性是classdumper.D
(而不是某些本地D
类)
PS:问题,精炼的:我刚刚注意到一些有趣的细节,这些细节可能指向这个问题的答案:
在酸洗程序dumper.py
中,print self.d
使用第一个dumper.py
程序(没有导入dumper
的程序)打印
。另一方面,在dumper.py
中执行import dumper
并使用dumper.C()
创建对象,使得print self.d
打印
:Python自动限定self.d
属性!因此,pickle
模块似乎在上述良好的取消勾选行为中没有任何作用
因此,问题是:在第二种情况下,Python为什么要将D()
转换为完全限定的dumper.D
?这是否有文档记录?当您的类在主模块中定义时,pickle希望在取消pickle时在主模块中找到它们。在第一种情况下,类是在主模块中定义的,因此当loader运行时,loader是主模块,pickle无法找到类。如果查看obj.pickle
的内容,您将看到name\uuuu main\uuuu
导出为C和D类的名称空间
在第二种情况下,dumper.py导入自身。现在您实际上已经定义了两组独立的C和D类:一组在\uuuu main\uuuu
命名空间中,另一组在转储程序
命名空间中。序列化转储程序
命名空间中的一个(查看obj.pickle
进行验证)
如果找不到名称空间,pickle将尝试动态导入名称空间,因此当loader.py运行时,pickle本身将导入dumper.py以及dumper.C和dumper.D类
由于您有两个单独的脚本,dumper.py和loader.py,因此在公共导入模块中定义它们共享的类才有意义:
普通的.py
加载器.py
dumper.py
请注意,即使dumper.py转储C()
,pickle也知道它是common.C
对象(请参见obj.pickle
)。当loader.py运行时,它将动态导入common.py并成功加载对象。下面是发生的情况:当从dumper.py
中导入转储程序
(或从转储程序导入C
)时,整个程序将再次解析(这可以通过在模块中插入打印来查看)。此行为是预期的,因为转储程序
不是已加载的模块(\uuuuuu main\uuuuu
被视为已加载)–它不在系统模块
中
如Mark的回答所示,导入模块自然会限定模块中定义的所有名称,因此在重新评估文件dumper.py
时,self.d=d()
被解释为属于类dumper.d
(这相当于在Mark的回答中解析common.py
)
因此,解释了import dumper
(或来自dumper import C
的)技巧,酸洗不仅完全符合classC
,而且也符合classD
。这使得通过外部程序取消勾选更容易
这还表明,在dumper.py
中完成的import dumper
会强制Python解释器对程序进行两次解析,这既不高效也不优雅。因此,在一个程序中对类进行pickle并在另一个程序中取消对它们的pickle可能是最好的方法,如Mark的回答所述:pickle类应该在一个单独的模块中。关于使用公共模块的好建议。真正的问题是原始问题中PS中现在的问题:当common.py
的代码位于dumper.py
中时,为什么self.d=d()
在创建dumper.C()
时完全由Python限定?您的common.py
可能会指出答案:在您的示例中,Python显然使self.d=d()
成为common.d
obj
class D(object):
pass
class C(object):
def __init__(self):
self.d = D()
import pickle
with open('obj.pickle','rb') as f:
obj = pickle.load(f)
print obj
import pickle
from common import C
with open('obj.pickle','wb') as f:
pickle.dump(C(),f)