绑定标量全局变量一 |
|
C
函数来操作内部对象?这在 Lua 4 中很容易,但 Lua 5 缺少大多数用于实现 Lua 4 的简单方法的元方法。
这里提供一个解决方案,它要求全局变量按名称绑定。有关不同实现,请参见绑定标量全局变量二。
这里的想法很简单:绑定标量不在全局表中;相反,getter 和 setter 函数被放置在另外两个表中,并且这些函数由全局表的__index
和__newindex
元方法调用。这不会对具有值的全局变量产生性能影响,并且希望对绑定变量的影响也相当有限。(代理可能是一个更好的名称——“绑定标量”一词来自 Perl。[1])
在这里,我注意使用现有全局表中任何现有的__index
和__newindex
元方法。希望其他现有的元方法无关紧要,但也可以处理它们。
-- BindScalar1 -- Version 0.2 -- RiciLake -- -- Change history -- -------------- -- Fixed the case where the globals table didn't have a metatable -- Corrected the behaviour on set where there was no existing -- __newindex metamethod so that it now rawsets the table -- Check to see if the old __index method is not a function -- to mimic the default behaviour -- Wrote a couple of quick example getters and setters -- Actually made sure it compiles and runs -- -- TODO -- ---- -- Actually debug it with real metatables -- Think of a setter that lets you set something -- -- BUGS -- ---- -- If you specify a getter and don't specify a setter, the binding stops -- working. It should be necessary to specify both. -- -- The API needs to be improved do local meta, getters, setters = {}, {}, {} local old_meta = getmetatable(getfenv()) local old_index, old_newindex if old_meta then old_index, old_newindex = old_meta.__index, old_meta.__newindex end -- at this point you have to populate the getters and setters table -- somehow, probably by getting them from your C code. -- Here is an example without C: -- the getter receives the name of the global as an argument local function get_time(k) if k == "gmt" then return os.date("!%c") else return os.date("%c") end end -- the setter actually receives the name and the proposed value -- but in this example we don't need them. local function set_time() error "You cannot change the time" end -- now put them into getters and setters. There should probably -- be a function to do that, something like: -- bind_scalar("now", get_time, set_time) getters.now = get_time getters.gmt = get_time setters.now = set_time setters.gmt = set_time -- Another example. Particular environment variables are made -- into globals. (Change this to USERNAME for Windows NT.) local function get_env(k) return os.getenv(k) end local function set_env(k, v) if os.setenv then os.setenv(k, v) else error "You cannot change environment variables on this platform." end end getters.USER = get_env setters.USER = set_env -- hmm? it's just an example -- It might be nice to change the calls below to object calls, -- such as getters[k](getters[k], k) -- For efficiency, you probably only want to do that lookup once. -- Here is the actual implementation of the metamethods. meta = {} if type(old_index) == "function" then function meta.__index(t, k) if getters[k] then return getters[k](k) else return old_index(t, k) end end elseif type(old_index) == "nil" then function meta.__index(t, k) if getters[k] then return getters[k](k) end end else function meta.__index(t, k) if getters[k] then return getters[k](k) else return old_index[k] end end end if old_newindex then function meta.__newindex(t, k, v) if setters[k] then setters[k](k, v) else old_newindex(t, k, v) end end else function meta.__newindex(t, k, v) if setters[k] then setters[k](k, v) else rawset(t, k, v) end end end setmetatable(getfenv(), meta) end
示例输出
-- now is deferred to a function. > print(now) Thu Jan 16 12:34:40 2003 -- so is gmt > print(gmt) Thu Jan 16 17:35:34 2003 -- setting "works"; the variable is read-only > now = "tomorrow" glue.lua:27: You cannot change the time stack traceback: [C]: in function `error' glue.lua:27: in function `?' glue.lua:91: in function <glue.lua:88> stdin:1: in main chunk [C]:[C] -- This mechanism might be useful in a CGI script, for example > print(USER) rlake -- Most platforms implement setenv but it's not ANSI standard. This -- would work if you patched the os library. > USER="root" glue.lua:44: You cannot change environment variables on this platform. stack traceback: [C]: in function `error' glue.lua:44: in function `?' glue.lua:91: in function <glue.lua:88> stdin:1: in main chunk [C]:[C] -- Ordinary globals continue to be ordinary. > print(j) nil > j = 7 > print(j) 7
脚注
这只有一个问题:__newindex 仅在之前未设置的值上调用,例如 x = "bla";x = "more bla" 将仅调用 __newindex 一次。--Anon
是的,这就是为什么它在使用 setter 函数时实际上不会设置值的原因。但是,在创建 setter 函数时,您必须首先确保相应的全局变量未设置;如果您想稍后添加 setter 函数,同样的事情也适用。试试看 :) --RiciLake