Python 当跨模块派生类时,名称解析是如何工作的?

Python 当跨模块派生类时,名称解析是如何工作的?,python,python-2.7,Python,Python 2.7,类B和C都派生自基类A,并且都不重写A的方法test()。B与A在同一模块中定义;C在单独的模块中定义。为什么调用B.test()会打印“hello”,而调用C.test()会失败?两个调用都不应该执行.test(),从而能够解析mod1命名空间中的符号“message”吗 我也很感激收到关于这种行为记录在哪里的提示,因为我一直无法找到任何东西。调用C.test()时名称是如何解析的,并且“消息”可以以某种方式注入其中一个名称空间吗 FWIW,我没有使用实例变量(例如set A.message=

类B和C都派生自基类A,并且都不重写A的方法test()。B与A在同一模块中定义;C在单独的模块中定义。为什么调用B.test()会打印“hello”,而调用C.test()会失败?两个调用都不应该执行.test(),从而能够解析mod1命名空间中的符号“message”吗

我也很感激收到关于这种行为记录在哪里的提示,因为我一直无法找到任何东西。调用C.test()时名称是如何解析的,并且“消息”可以以某种方式注入其中一个名称空间吗

FWIW,我没有使用实例变量(例如set A.message=“hello”)的原因是我想访问一个“全局”单例对象,而不想在每个其他对象中都有一个对它的显式引用

mod1.py:

import mod2

class A(object):
  def test(self):
    print message

class B(A):
  pass

if __name__ == "__main__":
  message = "hello"
  A().test()
  B().test()
  mod2.C().test()
mod2.py:

import mod1

class C(mod1.A):
  pass
输出为:

$ python mod1.py 
hello
hello
Traceback (most recent call last):
  File "mod1.py", line 14, in <module>
    mod2.C().test()
  File "mod1.py", line 5, in test
    print message
NameError: global name 'message' is not defined
$python mod1.py
你好
你好
回溯(最近一次呼叫最后一次):
文件“mod1.py”,第14行,在
mod2.C().test()
测试中的第5行文件“mod1.py”
打印消息
NameError:未定义全局名称“消息”

非常感谢

是否可以使用类属性而不是全局属性?以下作品

import mod2

class A(object):

    message = "Hello"  # Class attribute (not duplicated in instances)

    def test(self):
        print self.message  # Class A attribute can be overridden by subclasses

class B(A):
    pass

if __name__ == "__main__":

    A().test()
    B().test()
    mod2.C().test()
不使用globals更干净:在上面的代码中,
message
被显式地附加到它所使用的类


话虽如此,我也很好奇为什么
mod2.C().test()
找不到全局
消息


但是,如果删除了交叉导入(在
mod1.py
中没有主程序,也没有
importmod2
):从
mod3.py
导入
mod1
mod2
,执行
mod1.message=“Hello”
mod2.C().test()
。因此,我想知道这个问题是否与交叉导入无关……

EOL是正确的,将程序的“主要”部分移动到新文件mod3.py中确实可以使事情正常进行

进一步澄清了这个问题

在我最初的问题中,原来变量
message
存储在
\uuuu main\uuuu
模块名称空间中,因为mod1.py是作为脚本运行的。mod2导入mod1,但它获得一个单独的
mod1
名称空间,其中变量
message
不存在。下面的代码片段在将
消息
写入
mod1
的名称空间(我不建议在现实生活中这样做)时更清楚地说明了这一点,从而导致了预期的行为

import sys

class A(object):
  def test(self):
    print message

class B(A):
  pass

if __name__ == "__main__":
  import mod2
  message = "hello"
  sys.modules["mod1"].message = message
  A().test()
  B().test()
  mod2.C().test()
我认为现实世界中最好的修复方法是将程序的“主要”部分移动到一个单独的模块中,正如EOL所暗示的,或者:

class A(object):
  def test(self):
    print message

class B(A):
  pass

def main():
  global message
  message = "hello"
  A().test()
  B().test()

  # resolve circular import by importing in local scope
  import mod2
  mod2.C().test()

if __name__ == "__main__":
  # break into mod1 namespace from __main__ namespace
  import mod1
  mod1.main()

旁注:(其他)当您这样交叉导入时,问题很容易出现。例如,
pythonmod2.py
失败,因为在定义class
C
时尚未定义
mod1.A
。我的经验是,像这样的交叉导入通常表明某些代码在逻辑上会被移动到其他地方,直到没有交叉导入为止。你说的“我不想在每个其他对象中都有对它的显式引用”是什么意思?我不确定我是否理解使用类属性的问题:
a.message=“hello”
设置一个类属性,该类属性不存储在类实例中。@EOL,我现实世界中的“全局”问题是一个数据库句柄。我不希望将其存储在对象/类中,因为有许多对象和类型需要访问数据库。另一个问题是,我通过
多处理
队列
s传递这些对象,并且数据库句柄必须是特定于进程的,因此将其添加到对象/类是很麻烦的。最后,目标是基类是用户可扩展的,因此是跨模块的。理想情况下,我希望了解为什么我对名字决议的期望是错误的,并找出今后的方向。我明白了。是的,在这里使用一个全球性的。我不确定用户可扩展性的要求与交叉导入有何关系:我指的是这样一个事实:最好不要使用
mod1.py
do
importmod2
(如果
mod2.py
does
importmod1
)。我想知道这个问题是否与这个交叉导入无关。我想我现在已经解决了——请参阅下面的答案并感谢您的帮助。那么,这是否意味着在原始主程序中,
A().test()
使
消息
test()
函数中被解释为
sys.modules[''''.message
,但是
mod2.C().test()
使得
message
被解释为
mod1.message
?如果是这样的话,我很想知道解释器内部是如何工作的。
test()
的字节码只需读取
LOAD\u全局消息
:是什么允许解释器在
A().test()
mod2.C().test()
中对其进行不同的解释?@EOL,没错。mod1.py的可用性是
sys.modules['''u____']
sys.modules['mod1']
的两倍。
A.test()
有两个实例,每个模块一个。在本例中,
A().test()
调用前者,
mod2.C().test()
最终调用后者。如果您在python源代码中遵循ceval.c,它将显示
LOAD\u GLOBAL
从定义调用函数的任何位置读取全局符号表(这也是有文档记录的)。问题在于,由于
\uuuu main\uuuu
命名空间,调用函数
A.test()
有两个版本。这就是我的困惑!谢谢,现在一切都清楚了+1.谢谢你的回答。:)