我是来自C ++背景的Python新手。虽然我知道使用我的旧C ++知识尝试找到匹配的概念并不是Pythonic,但我认为这个问题仍然是一个普遍的问题:
在C ++下,有一个众所周知的问题叫做全局/静态变量初始化命令惨败,因为C ++无法决定哪个全局/静态变量首先在编译单元中初始化,因此全局/静态变量取决于另一个在不同的编译单元中,可能比它的依赖对应物更早地初始化,并且当依赖开始使用依赖对象提供的服务时,我们将有未定义的行为。在这里,我不想深入了解C ++如何解决这个问题。 :)
在Python世界中,我确实看到全局变量的使用,甚至跨越不同的.py文件,我看到的一个典型用例是:在一个.py文件和其他.py文件中初始化一个全局对象,代码只是无所畏惧地开始使用全局对象,假设它必须已在其他地方初始化,由于我上面指定的问题,在C ++下我自己肯定不会接受。
我不确定上述用例是否是Python(Pythonic)中的常见做法,以及Python如何解决这种全局变量初始化顺序问题?
非常感谢!
林
答案 0 :(得分:12)
Python导入从头到尾执行新的Python模块。后续导入仅导致sys.modules
中现有引用的副本,即使由于循环导入仍处于导入模块的中间。在圆形导入存在之前已初始化的模块属性(“全局变量”实际上在模块范围内)。
main.py
:
import a
a.py
:
var1 = 'foo'
import b
var2 = 'bar'
b.py
:
import a
print a.var1 # works
print a.var2 # fails
答案 1 :(得分:10)
在C ++下,有一个众所周知的问题叫做全局/静态变量初始化命令fiasco,因为C ++无法决定哪个全局/静态变量首先在编译单元中初始化,
我认为该声明突出了Python和C ++之间的关键区别:在Python中,没有不同的编译单元。我的意思是,在C ++中(如你所知),两个不同的源文件可能完全相互独立编译,因此如果你比较文件A中的一行和文件B中的一行,就没有什么好说的了。你将在计划中获得第一名。这有点像多线程的情况:你不能说线程1中的特定语句是在线程2中的特定语句之前还是之后执行。你可以说C ++程序是并行编译的。
相比之下,在Python中,执行从一个文件的顶部开始,并通过文件中的每个语句以明确定义的顺序进行,并在导入它们的位置分支到其他文件。实际上,您几乎可以将import
指令视为#include
,这样您就可以识别程序中所有源文件中所有代码行的执行顺序。 (嗯,它比这复杂一点,因为模块只在第一次导入时才真正执行,并且由于其他原因。)如果C ++程序是并行编译的,那么Python程序将被串行解释。
你的问题也触及了Python中模块的深层含义。 Python模块 - 它是单个.py
文件中的所有内容 - 是一个实际的对象。在单个源文件中在“全局”范围内声明的所有内容实际上都是该模块对象的属性。 Python中没有真正的全局范围。 (Python程序员经常说“全局”,实际上语言中有一个global
关键字,但它总是指的是当前模块的顶层。)我可以看到这是一个奇怪的概念习惯于来自C ++背景。我花了一些时间来习惯,来自Java,在这方面,Java比C ++更类似于Python。 (Java中也没有全局范围)
我将在Python中提到使用变量而不知道它是否已被初始化/定义是完全正常的。嗯,也许不正常,但在适当的情况下至少可以接受。在Python中,尝试使用未定义的变量会引发NameError
;你不会像在C或C ++中那样获得任意行为,因此你可以轻松处理这种情况。你可能会看到这种模式:
try:
duck.quack()
except NameError:
pass
如果duck
不存在则不执行任何操作。实际上,你更常见的是
try:
duck.quack()
except AttributeError:
pass
如果duck
没有名为quack
的方法,则不执行任何操作。 (AttributeError
是当您尝试访问对象的属性时获得的错误,但该对象没有该名称的任何属性。)这是Python中的类型检查所传递的:我们认为如果我们所有需要鸭子做的都是嘎嘎叫,我们可以问它嘎嘎叫,如果确实如此,我们不关心它是否真的是一只鸭子。 (它被称为鸭子打字; - )