快速掌握Lua 5.3 —— 环境
2016-02-05 17:12
691 查看
Q:_ENV
?
A:_ENV是一个普通的”table”,它其中存储了当前运行环境中所有的全局变量。
[code]type(_ENV) --> table for n in pairs(_ENV) do print(n) end -- 打印当前运行环境中所有全局变量。
我们平常在程序中所写的全局变量就存储在其中,全局变量名是”key”,全局变量值是”value”,
[code]a = 10 print(_ENV["a"]) --> 10
Q:如何更改运行环境?
A:全局变量的问题也是他是全局的,任何对其的修改都会影响到所有的程序。举个例子,当你规定全局变量的使用必须声明时,所有的程序就都需要遵循这一规则。如果你想使用一个库,那么如果你不声明的话就会报错。通过更改
_ENV,可以更改当前程序的运行环境,
[code]--[[ 请在文件中运行上述例子,如果在交互模式中运行,请用"do-end"包裹起来。 因为在交互模式中运行的每一行代码都是一个单独的"chunk", 所以"_ENV = {}"只对它自己的那一行"chunk"起作用。]] a = 1 -- create a global variable _ENV = {} -- change current environment to a new empty table print(a) --> attempt to call a nil value (global 'print')
print()也是一个全局函数,因为修改了环境变量,原先的
print()在
_G中,而修改后的环境变量集合中没有
print(),所以会报错。我们可以通过保存原先的运行环境,从而使用原先的环境变量,
[code]a = 1 -- create a global variable -- "a"存储在"_G"中,"_G.a"。 _ENV = {_G = _G} -- 新的环境变量中"_G"域存储原先的"_G"。 _G.print(a) --> nil -- "a"在"_G"中,不在当前的环境变量中。 _G.print(_G.a) --> 1
也可以使用”metatable”继承的方式实现,
[code]a = 1 -- create a global variable local newgt = {} -- create new environment -- 新的环境变量找不到的"key",去原先的"_G"中寻找。 setmetatable(newgt, {__index = _G}) _ENV = newgt print(a) --> 1 -- 如果你在重定义环境变量集合之前定义过全局变量,那么现在你也不用担心更改与之前同名的全局变量会造成问题。 -- continuing previous code a = 10 print(a) --> 10 print(_G.a) --> 1 _G.a = 20 print(_G.a) --> 20
关于”_ENV”与”_G”的关系,我理解的还不够透彻。下面引用的这段资料讲的可能会更详细些。
原文连接:【lua5.2技术干货】带你理解_ENV和_G, 不懂的同学来学习吧~
5.1之前, 全局变量存储在_G这个table中, 这样的操作:
a = 1
相当于:
_G[‘a’] = 1
但在5.2之后, 引入了_ENV叫做环境,与_G全局变量表产生了一些混淆,需要从原理上做一个理解。
在5.2中,
操作a = 1
相当于
_ENV[‘a’] = 1
这是一个最基础的认知改变,其次要格外注意_ENV不是全局变量,而是一个upvalue(非局部变量)。
其次,_ENV[‘_G’]指向了_ENV自身,这一目的是为了兼容5.1之前的版本,因为之前你也许会用到:
_G[‘a’] = 2 , 在5.2中, 这相当于_ENV[‘_G’][‘a’],为了避免5.1之前的老代码在5.2中运行错误,所以5.2设置了_ENV[‘_G’]=_ENV来兼容这个问题。然而你不要忘记_ENV[‘_G’]=_ENV,所以一切都顺理成章了。
在5.1中,我们可以为一段代码块(或者函数)设置环境,使用函数setfuncs,这样会导致那一段代码/函数访问全局变量的时候使用了setfuncs指定的table,而不是全局的_G。
在5.2中,setfuncs遭到了废弃,因为引入了_ENV。 通过在函数定义前覆盖_ENV变量即可为函数定义设置一个全新的环境,比如:
a = 3
function get_echo()
local _ENV={print=print, a = 2}
return function echo()
print(a)
end
end
get_echo()()
会打印2,而不是3,因为echo函数的环境被修改为{print=print, a=2},而print(a)相当于访问_ENV[‘a’](先忘掉那为了兼容而存在的_G)。
这就是_ENV的基本用法了。
另外,不得不提到lua的C支持中关于全局变量与环境的细节,只能简单描述,你必须自己试试才能记得清楚。
lua_setglobal/lua_getglobal都是操作lua_State注册表中LUA_RIDX_GLOBALS伪索引指向的全局变量表,与lua中访问_ENV[‘a’]或者a是不同的。
lua_load加载lua代码后会返回一个函数,默认会给这个函数设置一个upvalue就叫_ENV,起值是LUA_RIDX_GLOBALS的全局变量表,你可以lua_setupvalue设置这个函数的upvalue,即下标1的upvalue,因为这个位置是这个函数的_ENV表存放位置(你可以通过lua_setupvalue的返回值印证这一点)
这里巧妙的是,lua_State会在创建时保证LUA_RIDX_GLOBALS的全局变量表中包含一个指向自己的_G元素,这样就保证了在不调用lua_setupvalue的情况下该返回函数的_ENV[‘_G’]是指向自己的,即LUA_RIDX_GLOBALS这个全局表。(其实你的lua解释器就是简单的lua_load后pcall的,对于一个刚启动lua_State来说是没有_ENV的,是lua解释器load你的代码时自动给带上的_ENV,其值是lua_state的LUA_RIDX_GLOBALS全局表。)
一些有意思的东西是需要你自己摸索的,lua语言自身就很简练,并且所有东西都不是什么神秘的事情,可以通过读源码或者试验摸索得到。
最后,提一下,lua_state启动后在注册表里LUA_RIDX_GLOBALS下标存放的全局表一定有一个元素是指向自己的,即_G.
附加:
1、_ENV中存储着它本身,
[code]_ENV == _ENV._G --> true _ENV == _ENV["_G"] --> true
2、为一个不存在的”table”中的元素赋值会报错,
[code]print(t) --> nil t.x.y = 10 --> attempt to index a nil value (global 't')
但是通过操作
_ENV,我们可以解决这一问题,
[code]function getfield (f) local v = _ENV -- start with the table of globals --[[ 匹配模式中未指定捕获,"string.gmatch()"会返回匹配的值, 即"t.x.y"会匹配3次,每次返回"t"、"x"、"y"。]] for w in string.gmatch(f, "[%w_]+") do v = v[w] -- get the next table or get the final value. end return v end function setfield (f, v) local t = _ENV -- start with the table of globals --[[ 匹配模式中指定了捕获,"string.gmatch()"依次返回捕获的值, 即"t.x.y"会匹配3次,每次返回"t"和"."、"x"和"."、"y"和"nil"。]] for w, d in string.gmatch(f, "([%w_]+)(.?)") do if d == "." then -- 有"."说明没有到最后,到最后时是"nil"。 t[w] = t[w] or {} -- 如果此"table"不存在,则创建。 t = t[w] -- get the table else -- 为"nil",到了最后一个域。 t[w] = v -- do the assignment end end end print(t) --> nil setfield("t.x.y", 10) print(t.x.y) --> 10 print(getfield("t.x.y")) --> 10
3、Lua中的全局变量不需要声明。虽然这对一些小程序来说很方便,但程序很大时,一个简单的拼写错误可能引起”bug”并且很难发现。然而,如果我们喜欢,可以使用”metatable”来改变这种行为,
[code]--[[ 此函数一定要写在下面的"setmetatable()"前面, 因为这里的"declare()"也是个全局函数,写在其后依旧会报错。]] function declare (name, initval) --[[ "_ENV"中未声明的全局变量的默认值都是"nil", 为了与已声明却未给初始值的全局变量的默认值区别开,后者的默认值是"false"。]] rawset(_ENV, name, initval or false) end setmetatable(_ENV, { __newindex = function (_, n) error("attempt to write to undeclared variable " .. n, 2) end, __index = function (_, n) error("attempt to read undeclared variable " .. n, 2) end, }) a = 7 --> attempt to write to undeclared variable a declare("a") print(a) --> false a = 7 print(a) --> 7 -- 此种方式下最好不要让"a = nil",因为一旦这样,已声明的变量"a"在"_ENV"中就变成了未声明的变量,再次使用时就会报错。 a = nil a = 1 --> attempt to write to undeclared variable a
现在,如果想测试一个变量是否存在,不能简单的将这个变量与”nil”比较,需要使用
rawget(),
[code]if(x == nil) then --> attempt to read undeclared variable x print("Variable does not exist.") end if(rawget(_ENV, x) == nil) then print("Variable does not exist.") --> Variable does not exist. end
如果希望变量在使用前需要声明,同时又希望未给默认值的已声明变量的默认值是”nil”,那么可以使用一个辅助表保存已声明的变量,
[code]local declaredNames = {} function declare (name, initval) rawset(_ENV, name, initval) declaredNames[name] = true end setmetatable(_ENV, { __newindex = function (t, n, v) if not declaredNames then error("attempt to write to undeclared var. \"" .. n .. "\"", 2) else rawset(t, n, v) -- do the actual set end end, __index = function (_, n) if not declaredNames then error("attempt to read undeclared var. \"" .. n .. "\"", 2) else return nil end end, }) a = 7 --> attempt to write to undeclared var. "a" declare("a") print(a) --> nil a = 7 print(a) --> 7
4、当你创建一个新的函数时,他从创建他的函数继承了环境变量。所以,如果一个”chunk”改变了他自己的环境,这个”chunk”所有在改变之后定义的函数都共享相同的环境,都会受到影响,这对创建命名空间是非常有用的机制。
相关文章推荐
- luajit ffi 小结
- luajit ios arm64 编译bytecode
- 【Lua学习笔记】 --> 《类型与值、表达式与基本语法》
- lua实现多继承
- lua 类实现
- lua 类继承和实现
- Lua多重继承
- 项目记录19--tolua封装自己的View层01记版录
- xcode中打开lua代码有色差
- Lua开源项目
- 理解LUA的C API的最好的学习方法。
- iOS开发与Lua结合的使用
- tolua namespace
- programming in lua 之 lua api函数
- Lintcode: Expression Evaluation (Basic Calculator III)
- lua垃圾回收机制
- Lua中的元表与元方法
- 用LuaBridge为Lua绑定C/C++对象
- lua class
- Lua5.3 异或操作的一个坑