import 与全局变量


    import 与全局变量
   
Created:  Fang lungang 07/30/2007
Modified: Fang lungang 08/02/2007 09:19>
[url=file:///C:/lgfang/myhome/homepage/tmp/py-import-global.html#sec1]“出乎意料”的结果[/url]
[url=file:///C:/lgfang/myhome/homepage/tmp/py-import-global.html#sec2]原因[/url]
[url=file:///C:/lgfang/myhome/homepage/tmp/py-import-global.html#sec3]验证[/url]
[url=file:///C:/lgfang/myhome/homepage/tmp/py-import-global.html#sec4]"from M import X" vs. "import M"[/url]
由于 C、C++ 先入为主,学习 python 时总自觉、不自觉的把 python 的特性和
C、C++ 的进行类比。看到 import 就和 C 的 include 混到一起。前两天在
comp.lang.python 上看到一个讨论才对 import 真正理解了一点点。
“出乎意料”的结果
先看一个例子。下面代码的本意是想在 module m1 中定义一个全局变量 ref 然
后在 module m2 中 import 它并使用。但实际却并非如此。
# m1.py
ref = ["init"]
def changeRef():
    global ref
    ref = ["reference changed"]
def refInM1():
    return "In M1:%s;\t" % ref
# m2.py
from m1 import *
print "At the beginning:\t", refInM1(), "In M2:", ref
changeRef()
print "After changeRef:\t", refInM1(), "In M2:", ref
$ python m2.py
At the beginning:       In M1:['init']; In M2: ['init']
After changeRef:        In M1:['reference changed'];    In M2: ['init']
从上述运行结果可以看到在 m1 和 m2 分别有一份 ref 的拷贝, m1 中的函数修
改只是 m1 中的那份拷贝。可以推断,如果在 m2 中修改 ref,改的肯定是本
module 的 ref。
原因
上述现象并不是一个 bug,python 的语法就是如此。
在 python 中没有所谓的全局变量。 import 一个名字(变量名、函数名等等)
的实际作用是 (Python Reference Manual,sec. 6.12):
Import statements are executed in two steps: (1) find a module,
and initialize it if necessary; (2) define a name or names in the
local namespace (of the scope where the import statement occurs).
本文只关心第二步。考虑到在 python 中名字就是指向对象的引用,这一步可以
换言之: import 会在当前 scope 定义一个引用,这个引用和被 import 的那个
引用指向同一个对象。
明确了这个再回头来看前面的代码,结果就不难理解了(图示如下):
1, Module m1 创建了一个列表对象 (["init"],暂且称为 obj1) 并且定义了一
个名字(引用) ref 指向它。
2, Module m2 在 import m1 的过程中也定义了一个名字 ref,它也指向 obj1。
3, Module m2 调用 m1.changeRef。这个函数因为在 module m1 中,所以它所访
问的是 m1 中的 ref。它修改了 m1.ref, 让这个 ref 指向另一个对象
(["reference changed"])。
注意,在上述过程中 m2.ref 本身并没有改变(还是指向 obj1);而且它所指向
的对象(obj1)也没有改变。所以 m1.ref 和 m2.ref 打印出来的结果不一样。
At the begining  ================> After changeRef called
Module 1                           Module 1
,---------,                        ,---------,
| ...     |                        | ...     |
|         |                        |         |     ,-------------------,
| ref ------------\                | ref --------->|"reference changed"|
|         |       |                |         |     `-------------------`
| ...     |       |                | ...     |
|         |       |                |         |
`---------`       V  obj1          `---------`         obj1
                ,----------,                       ,----------,
                |"init"    |                       |"init"    |
Module 2        `----------`       Module 2        `----------`
,---------,       ^                ,---------,       ^
| ...     |       |                | ...     |       |
|         |       |                |         |       |
| ref ------------/                | ref ------------/
|         |                        |         |
| ...     |                        | ...     |
|         |                        |         |
`---------`                        `---------`
验证
根据前面分析,可以得出:如果 m1.changeRef 不是改变 m1.ref 的指向而是
改变 m1.ref 所指的对象(obj1),m1.ref 和 m2.ref 打印出来的结果就应该
是一样的(因为它们都指向同一个对象)。我们可以把代码稍微修改一下来验证。
# m1_2.py
ref = ["init"]
def changeValue():
    ref[0] = "value changed"
def refInM1():
    return "In M1:%s;\t" % ref
# m2_2.py
from m1_2 import *
print "At the beginning:\t", refInM1(), "In M2:", ref
changeValue()
print "After changeValue:\t", refInM1(), "In M2:", ref
$ python m2_2.py
At the beginning:       In M1:['init']; In M2: ['init']
After changeValue:      In M1:['value changed'];        In M2: ['value changed']
"from M import X" vs. "import M"
从以上讨论我还想到一个问题:import 的两种形式中,就本文讨论的焦点而言,
import M 比 from M import X 要好。因为采用前者就不太可能犯本文一开始所
犯的错误了。当然,赋值给一个 Module 的名字的变态情况不算。