LUA中的全局变量环境

LUA中的全局变量环境  

2011-01-24 18:33:00|  分类: LUA|举报|字号 订阅

LUA中文手册 Chapter14笔记。

一、全局变量的环境

LUA这货在全局变量的实现方式上用了一个绝对让我喜闻乐见的做法,复用已有的table机制,将全局变量都保存在_G表中。这样做好处很多。

从语言实现角度来说,不需要为了全局变量单独去做一份实现,只要复用下table机制即可。

从使用角度来说,虽然你是全局变量看起来较为牛逼(local变量内牛满面的蹲在一边哭)一点,但是你也不幸的被放在了table中。于是所有针对一个table可以做的事情针对全局量一样可以做:

1.变量动态名称访问: 运行期才知道那全局量叫啥,访问起来吃力。现在好了,因为放在了table中,所以只要写上 _G[varname] 就可以访问了,写上 _G[varname]=ABC就可以赋值了。不用再去靠拼接字符串来chunk代码了。

2.metatable可以使用。虽然又提到了metatable,不用说这里指代的是table御用__index和__newindex运算符。通过实现这两个运算符,我们可以对全局变量_G表做许多的事情,其中最常见的就是,通过重新实现__index和__newindex来为LUA中的全局变量使用规则进行修改,加上强类型语言中的“先声明后使用,不声明不能用”的规则。这点在手册上有详细的描述不再COPY。

 

二、全局变量的局部环境

       在C++中变量的作用域是从内向外查找,并且具备遮蔽效果。在lua中全局变量方面,有一个特点很有意思,就是允许每一个函数具有自己的局部“全局环境”,听着冲突其实不然。“局部”意指函数的内部,“全局环境”就是指全局变量保存的_G表。也就是说LUA允许每一个函数内部使用一个私有的_G表,在文章中,这被称作是函数的环境。setfenv()可以更改一个指定函数的环境,第一个参数为函数名,第二个参数为这个函数的私有_G名,可以先初始化好再传递过去。第一个参数也可以是一个数字,1表示当前正在执行的这个函数,2表示当前函数的调用者,3表示调用者的调用者(其实这个数字是栈顶的活动函数调用层次)。

      不过要注意一点,调用setfenv()设置了私有_G表后,原来的_G表默认就不可访问了,这个时候很多原来_G中的函数你都无法使用,比如print。因此你可以或者在私有_G表中保存原始_G表,或者为私有_G表设置__index操作符指向原始_G表,相比来说我更喜欢后者,优雅且省事(懒%……)。

ps, 感慨下,table是LUA中的唯一复杂结构,所有复杂的数据结构和描述都要基于此。__index和__newindex是table非常重要的两个metatable运算符没有之一。

唔,今日功课完毕,收拾收拾准备下班走人了……









Lua 学习笔记:沙盒

November 5, 2012 分类:技术

背景知识

Lua 给我的感觉是:各种内置函数和标准库的存在感都是比较强的。如果执行这句:

for name in pairs(_G) do print(_G) end

就会把各种环境中已存在名称的打印出来:

  • 全局变量:比如字符串 _VERSION
  • 内置函数:比如 printtonumberdofile 之类。
  • 模块名称:比如 stringiocoroutine 之类。

这里的全局变量 _G 就是存放环境的表(于是会有 _G 中存在着 _G._G 的递归)。

于是,平时对于全局变量的访问就可以等同于对 _G 表进行索引:

value = _G[varname]  --> value = varname
_G[varname] = value  --> varname = value

改变函数的环境

函数的上下文环境可以通过 setfenv(f, table) 函数改变,其中 table 是新的环境表,f 表示需要被改变环境的函数。如果 f 是数字,则将其视为堆栈层级(Stack Level),从而指明函数(1 为当前函数,2 为上一级函数):

a = 3          -- 全局变量 a
setfenv(1, {}) -- 将当前函数的环境表改为空表
print(a)       -- 出错,因为当前环境表中 print 已经不存在了

没错,不仅是 a 不存在,连 print 都一块儿不存在了。如果需要引用以前的 print 则需要在新的环境表中放入线索:

a = 3
setfenv(1, { g = _G })
g.print(a)             -- 输出 nil
g.print(g.a)           -- 输出 3

沙盒

于是,出于安全或者改变一些内置函数行为的目的,需要在执行 Lua 代码时改变其环境时便可以使用 setfenv 函数。仅将你认为安全的函数或者新的实现加入新环境表中:

local env = {}  -- 沙盒环境表,按需要添入允许的函数

function run_sandbox(code)
  local func, message = loadstring(code)
  if not func then return nil, message end  -- 传入代码本身错误
  setfenv(func, env)
  return pcall(func)
end

Lua 5.2 的 _ENV 变量

Lua 5.2 中所有对全局变量 var 的访问都会在语法上翻译为 _ENV.var。而 _ENV 本身被认为是处于当前块外的一个局部变量。(于是只要你自己定义一个名为 _ENV 的变量,就自动成为了其后代码所处的「环境」(enviroment)。另有一个「全局环境」(global enviroment)的概念,指初始的 _G 表。)

Lua 的作者之一 Roberto Ierusalimschy 同志在介绍 Lua 5.2 时说:

the new scheme, with _ENV, allows the main benefit of setfenv with a little more than syntactic sugar.

就我的理解来说,优点就是原先虚无缥缈只能通过 setfenvgetfenv 访问的所谓「环境」终于实体化为一个始终存在的变量 _ENV 了。

于是以下两个函数内容大致是一样的:

-- Lua 5.1
function foobar()
  setfenv(1, {})
  -- code here
end

-- Lua 5.2
function foobar()
  local _ENV = {}
  -- code here
end

而更进一步的是,5.2 中对 load 函数作出了修改。(包括但不限于 :))合并了 loadstring 功能,并可以在参数中指定所使用的环境表:

local func, message = load(code, nil, "t", env)

参考

  • http://lua-users.org/wiki/SandBoxes
  • 云风的 Blog: lua 5.2 的 _ENV

Lua

已有 2 条评论 »

  1. Tristan Tristan
    June 27th, 2013 at 10:45 am

    一直在找lua5.2关于全局变量的资料,中文博客,一群SB就知道粘贴赋值lua文档,还注明转载表明出处,忒恶心我了
    感谢LZ,吐槽下自己,其实应该去自己看lua官方文档的



你可能感兴趣的:(lua)