2008年12月9日
今天下午调试程序的时候,遇到了一件非常奇怪的事。弄清楚了之后,才发现原来是Lua中Module中自有环境的问题。
大体情况是这样的,我在主程序中设定的全局变量,在模块文件中可以访问到,并修改了这个全局变量的值,但是在模块调用返回后,再次使用这个全局变量的值,发现它没有被赋值,没有被改变。究其原因,发现是因为Lua的模块里面,采用了自己的全局环境(这个全局环境会将主程序中的全局环境做为备选查找表,即使用__index联系),这与主程序中的全局环境是不一样的(两个不同的表)。模块中如果有一个变量被第一次赋值的话,Lua会认为是在模块的环境中新建一个全局变量,即使这个全局变量与主程序中的全局变量重名,它也不会去引用主程序中的全局变量,然后赋值。
下面举几一个sample来解释这种情况:
主程序文件: test.lua
另一个文件: a.lua
模块文件: b.lua
运行 lua test.lua 后,结果如下:
而如果将模块文件 b.lua 改成如下形式:
那么结果如下:
可以看到,程序修改之前没有对主程序全局变量进行赋值,而修改之后,给主程序全局变量赋了值。
通过本例,我们还可知道,require 既可用来加载模块文件(包括动态链接库模块),也可用来加载普通的代码块文件。模块文件加载进来后,使用一个引用来调用模块中的方法和变量。而普通的代码块文件在被require调入后,就相当于(如果有Local变量,则不完全是)成了主程序代码的一部分,全局变量可以贯穿使用。(非本地)函数也可以直接调用。
Lua的如下语句:
实际上进行了两个操作,首先是对GGG的引用,如果在当前模块中找不到它的定义,就会通过__index元方法查找到_G中去,如果在_G中找到了,那么就引用_G中的这个GGG变量,并且给它添加一个元素值20。而如果在_G中也没有找到定义,那程序会认为是第一次碰到GGG这个变量,第一次碰到这个变量就要去引用它,并且是引用它其中的一个元素,便会报错。所以,类似这样的数据元素赋值的操作实际上是:引用加索引和赋值三个操作的结合。
而简单的赋值语句就不一样了:
这句,会引用GGG,GGG在当前模块找不到的话,会到_G中去找。它也会引用SKU,但它发现,这是这个模块中的第一次出现的变量,并且被赋值(写),于是会当成是一个新的变量来创建。因此,这里的SKU,并非_G中的那个SKU,而仅仅是模块内部的一个变量。究根揭底,还是因为模块的环境与主环境之间只有__index(读回溯),而没有__newindex(写回溯)的缘故。
今天下午调试程序的时候,遇到了一件非常奇怪的事。弄清楚了之后,才发现原来是Lua中Module中自有环境的问题。
大体情况是这样的,我在主程序中设定的全局变量,在模块文件中可以访问到,并修改了这个全局变量的值,但是在模块调用返回后,再次使用这个全局变量的值,发现它没有被赋值,没有被改变。究其原因,发现是因为Lua的模块里面,采用了自己的全局环境(这个全局环境会将主程序中的全局环境做为备选查找表,即使用__index联系),这与主程序中的全局环境是不一样的(两个不同的表)。模块中如果有一个变量被第一次赋值的话,Lua会认为是在模块的环境中新建一个全局变量,即使这个全局变量与主程序中的全局变量重名,它也不会去引用主程序中的全局变量,然后赋值。
下面举几一个sample来解释这种情况:
主程序文件: test.lua
- require "a"
- print("================================")
- print(GGG[1])
- print(GGG[2])
- print(SKU[1])
- print(SKU[2])
- print("================================")
- f = require "b"
- f.run()
- print(GGG[1])
- print(GGG[2])
- print(SKU[1])
- print(SKU[2])
- print("================================")
- GGG = {}
- GGG[1] = 10
- SKU = {}
- module(..., package.seeall)
- function run()
- print("--------------------------------")
- GGG[2] = 20
- print(GGG[1])
- print(GGG[2])
- SKU = GGG
- print(SKU[1])
- print(SKU[2])
- print("--------------------------------")
- return 0
- end
- ================================
- 10
- nil
- nil
- nil
- ================================
- --------------------------------
- 10
- 20
- 10
- 20
- --------------------------------
- 10
- 20
- nil
- nil
- ================================
- module(..., package.seeall)
- function run()
- print("--------------------------------")
- GGG[2] = 20
- print(GGG[1])
- print(GGG[2])
- _G.SKU = GGG
- print(SKU[1])
- print(SKU[2])
- print("--------------------------------")
- return 0
- end
那么结果如下:
- ================================
- 10
- nil
- nil
- nil
- ================================
- --------------------------------
- 10
- 20
- 10
- 20
- --------------------------------
- 10
- 20
- 10
- 20
- ================================
可以看到,程序修改之前没有对主程序全局变量进行赋值,而修改之后,给主程序全局变量赋了值。
通过本例,我们还可知道,require 既可用来加载模块文件(包括动态链接库模块),也可用来加载普通的代码块文件。模块文件加载进来后,使用一个引用来调用模块中的方法和变量。而普通的代码块文件在被require调入后,就相当于(如果有Local变量,则不完全是)成了主程序代码的一部分,全局变量可以贯穿使用。(非本地)函数也可以直接调用。
Lua的如下语句:
- GGG[2] = 20
而简单的赋值语句就不一样了:
- SKU = GGG