• 友链

  • 首页

  • 文章归档
h u a n b l o g
h u a n b l o g

欢

HI,Friend

04月
21
Lua

Lua反射

发表于 2022-04-21 • 字数统计 5922 • 被 3,132 人看爆

概念

反射是程序用来检查和修改其自身某些部分的能力

Lua中几种反射机制

  • 环境允许运行时观察全局变量;
  • 诸如type和pairs这样的函数允许运行时检查和遍历未知数据结构;
  • 诸如load和require这样的函数允许程序在自身中追加代码或更新代码

调试库

  • 自省函数:允许我们检查一个正在运行中的程序的几个方面,例如活动函数的栈、当前正在执行的代码行、局部变量的名称和值。
  • 钩子:则允许我们跟踪一个程序的执行。

自省机制

自省函数:getinfo
该函数的第一个参数可以是一个函数或一个栈层次。

当为某个函数foo调用debug.getinfo(foo)时,该函数会返回一个包含与该函数有关的一些数据的表。这个表可能具有以下字段。

  • source:该字段用于说明函数定义的位置。如果函数定义在一个字符串中(通过调用load ),那么source就是这个字符串;如果函数定义在一个文件中,那么source就是使用@作为前缀的文件名。
  • short_src:该字段是source的精简版本(最多60个字符),对于错误信息十分有用。
  • linedefined:该字段是该函数定义在源代码中第一行的行号。
  • lastlinedefined:该字段是该函数定义在源代码中最后一行的行号。
  • what:该字段用于说明函数的类型。如果foo是一个普通的Lua函数,则为"Lua";如果是一个C函数,则为"C";如果是一个Lua语言代码段的主要部分,则为"main"。
  • name:该字段是该函数的一个适当的名称,例如保存该函数的全局变量的名称。
  • namewhat:该字段用于说明上一个字段R的含义,可能是"global" 、"local"、" method"、"field"或""(空字符串)。空字符串表示Lua语言找不到该函数的名称。
  • nups:该字段是该函数的上值的个数。
  • nparams:该字段是该函数的参数个数。
  • isvararg:该字段表明该函数是否为可变长参数函数(一个布尔值)。
  • activelines:该字段是一个包含该函数所有活跃行的集合。活跃行是指除空行和只包含注释的行外的其他行(该字段的典型用法是用于设置断点。大多数调试器不允许在活跃行外设置断点,因为非活跌行是不可达的)。
  • func:该字段是该函数本身。

当foo是一个C函数时,Lua语言没有多少关于该函数的信息。对于这种函数,只有字段what、name、namewhat、nups和 func是有意义的。
当使用一个数字n作为参数调用函数debug.getinfo(n)时,可以得到有关相应栈层次上活跃函数的数据。如果n大于栈中活跃函数的数量,那么函数debug.getinfo返回nil。

当通过带有栈层次的debug.getinfo查询一个活跃函数时,返回的表中还有两个额外字段:

  • currentline:表示当前该函数正在执行的代码所在的行;
  • istailcall(一个布尔值):如果为真则表示函数是被尾调用所调起(在这种情况下,函数的真实调用者不再位于栈中)。

函数getinfo有一个可选的第二参数,该参数用于指定希望获取哪些信息。这个参数是一个字符串其中每个字母代表选择一组字段

字符代表的字段
n选择name和namewhat
f选择func
S选择source、short_src、what、linedefined和lastlinedefined
l选择currentline
L选择activelines
u选择nup、nparams和isvararg

例:打印出了活跃栈的栈回溯:

function traceback ()
    for level = 1, math.huge do
        local info = debug.getinfo(level, "Sl")
        if not info then break end
        if info.what == "C" then            --是否是C函数?
            print(string.format("%d\tC function", level))
        else -- Lua函数
            print(string.format("%d\t[%s ]:%d ", level, info.short_src, info.currentline))
        end
    end
end


print(debug.traceback())

访问局部变量

函数:debug.getlocal来检查任意活跃函数的局部变量。
有两个参数:一个是要查询函数的栈层次,另一个是变量的索引
返回值:变量名和变量。如果变量索引大于活跃变量的数量,那么函数getlocal返回nil。如果栈层次无效,则会抛出异常(我们可以使用函数debug.getinfo来检查栈层次是否有效)。

Lua语言按局部变量在函数中的出现顺序对它们进行编号,但编号只限于在函数当前作用域中活跃的变量。

例如,考虑如下的代码:

function foo (a, b)
    local x
    do local c = a - b end
    local a = 1
    while true do
        local name, value = debug.getlocal(1, a)
        if not name then break end
        print(name, value)
        a = a + 1
    end
end

foo(10, 20)         
--[[
a	10
b	20
x	nil
a	4
]]

索引为1的变量是a(第一个参数),索引为2的变量b,索引为3的变量是x,索引为4的变量是内层的a。在getlocal被调用的时候,c已经离开了作用域,而name和value还未出现于作用域内(请注意,局部变量只在初始化后才可见)。

可以通过函数debug.setlocal改变局部变量的值,该函数的前两个参数与getlocal相同,分别是栈层次和变量索引,而第三个参数是该局部变量的新值。该函数的返回值是变量名,如果变量索引超出了范围则返回nil。

访问非局部变量

函数:getupvalue
第一个参数是函数(也可称为闭包),第二个参数是变量索引
也可用于更新非布局变量的值,也就是第三个参数
总的来说有三个参数:闭包、变量索引、新值
function getvarvalue (name,level, isenv)
    local value
    local found = false
    level = (level or 1) + 1

    --尝试局部变量
    for i = 1, math.huge do
        local n, v = debug.getlocal(level, i) 
        if not n then break end
        if n == name then
            value = v
            found = true
        end
    end

    if found then return "local" , value end

    --尝试非局部变量
    local func = debug.getinfo(level,"f" ).func
    for i = 1, math.huge do
        local n, v = debug.getupvalue(func, i)
        if not n then break end
        if n == name then return "upvalue ", v end
    end

    if isenv then return "noenv" end        --避免循环

    --没找到;从环境中获取值
    local _, env = getvarvalue("_ENV" , level,true)
    if env then
        return "global", env[name]
    else        --没有有效的_ENV
        return "noenv"
    end
end

local a = 4; 
print(getvarvalue("a"))       --> local
a = "xx"; 
print(getvarvalue("a"))       --> global    xx

参数level指明在哪个栈层次中寻找函数, 默认为1
  该函数首先查找局部变量。如果有多个局部变量的名称与给定的名称相同,则获取具有最大索引的那个局部变量。因此,函数必须执行完整个循环。如果找不到指定名称的局部变量,那么就查找非局部变量。为了遍历非局部变量,该函数使用debug.getinfo函数获取调用闭包,然后遍历非局部变量。最后,如果还是找不到指定名字的非局部变量,就检索全局变量:该函数递归地调用自己来访问合适的__ENV变量并在相应环境中查找指定的名字。
  参数isenv避免了一个诡异的问题。该参数用于说明我们是否处于一个从__ENV变量中查询全局名称的递归调用中。一个不使用全局变量的函数可能没有上值__ENV。在这种情况下,如果我们试图把__ENV当作全局变量来查询,那么由于我们需要__ENV来得到其自身的值,所以可能会陷入无限递归循环。因此,当isenv为真且函数getvarvalue找不到局部变量或上值时,getvarvalue就不应该再尝试全局变量。

访问其它协程

采用自省函数,可接受一个可选的协程作为第一个参数
co = coroutine.create(function ()
    local x = 10
    coroutine.yield()
    error("some error")
end)

coroutine.resume(co)
print(debug.traceback(co))
--[[
    stack traceback :
        [C]: in function 'yield'
        temp: 3: in function <temp : 1>
]]

-- 再次唤醒协程
print(coroutine.resume(co))         --> false temp :4: some error
--输出栈回溯
--[[
    stack traceback :
        [C]: in function 'error'
        temp :4: in function <temp : 1>
]]

--可检查局部变量
print(debug.getlocal(co, 1, 1))     --> x   10

钩子

调试库中的钩子机制允许用户注册一个钩子函数,这个钩子函数会在程序运行中某个特定事件发生时被调用。

四种事件能够触发一个钩子:

  • 每当调用一个函数时产生的call事件;
  • 每当函数返回时产生的return事件;
  • 每当开始执行一行新代码时产生的line事件;
  • 执行完指定数量的指令后产生的count事件。

要注册一个钩子,需要用两个或三个参数来调用函数debug.sethook:

  • 第一个参数是钩子函数
  • 第二个参数是描述要监控事件的掩码字符串
  • 第三个参数是一个用于描述以何种频度获取count事件的可选数字。

如果要监控call、return和line事件,那么需要把这几个事件的首字母(c、r或1)放入掩码字符串。

如果要监控count事件,则只需要在第三个参数中指定一个计数器。

如果要关闭钩子,只需不带任何参数地调用函数sethook即可。

debug.sethook(print, "l")       -- 为print安装构造函数,在line事件发生时调用它

例:使用函数getinfo获取当前文件名并添加到输出中:

function trace (event,line)
    local s = debug.getinfo(2).short_src
    print(s .. ":" .. line)
end

debug.sethook(trace, "l")

例:使用函数debug.debug,提供一个能够执行任意Lua语言命令的提示符

function debug1 () 
    while true do
        io.write(" debug> ")
        local line = io.read()
        if line == "cont" then break end
        assert(load(line)) ()
    end
end

当用户输入“命令”cont时,函数返回。

分享到:
设计模式-工厂模式
Lua协程
  • 文章目录
  • 站点概览
欢

网红 欢

你能抓到我么?

Email RSS
看爆 Top5
  • mac系统版本与Xcode版本有冲突 4,079次看爆
  • JAVA_HOME环境配置问题 3,729次看爆
  • AssetBundle使用 3,499次看爆
  • VSCode配置C++开发环境 3,256次看爆
  • Lua反射 3,133次看爆

Copyright © 2025 欢 粤ICP备2020105803号-1

由 Halo 强力驱动 · Theme by Sagiri · 站点地图