在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

(可以直接跳到问题,再往下跳,跳过介绍。)

从用户定义的类中提取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:
    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决定pickle
self.D
属性是class
dumper.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
)技巧,酸洗不仅完全符合class
C
,而且也符合class
D
。这使得通过外部程序取消勾选更容易


这还表明,在
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)