Lua 中的 __index、__newindex、rawget 与 rawset 介绍

原文地址:Lua 中的 __index、__newindex、rawget 与 rawset 介绍

欢迎参观我的网站:无敌牛 -- 技术/著作/典籍/分享等

在 Lua 中,表(table)是最核心的数据结构。通过为其设置"元表"(metatable),我们可以改变表在某些操作下的默认行为。其中,__index__newindex 是两个最常用的元方法,而 rawgetrawset 是与之配套的"绕过元表"的操作函数。理解它们的作用和关系,有助于更灵活、安全地使用 Lua 表。


一、__index ------ 读取不存在的键时的行为

当访问一个表中不存在的键时(例如 t.keyt["key"]),如果该表设置了元表,且元表中包含 __index 字段,Lua 就会调用它。

__index 可以是一个函数 ,也可以是一个

示例 1:__index 是函数

复制代码
local t = {}
local mt = {
    __index = function(tbl, key)
        print("访问了不存在的键:", key)
        return nil
    end
}
setmetatable(t, mt)

print(t.name)  -- 输出: 访问了不存在的键: name
               -- 然后输出: nil

示例 2:__index 是表(常用于"默认值"或"继承")

复制代码
local defaults = { x = 0, y = 0 }
local obj = {}
setmetatable(obj, { __index = defaults })

print(obj.x)  -- 0(从 defaults 获取)
print(obj.z)  -- nil(defaults 里也没有)

注意:如果键在表中已存在,则不会触发 __index


二、__newindex ------ 写入不存在的键时的行为

当给一个表中原本不存在的键 赋值时(例如 t.key = value),如果该表有元表且定义了 __newindex,Lua 会调用它,而不是直接赋值。

__index 一样,__newindex 也可以是函数或表。

示例 1:__newindex 是函数(常用于拦截、校验、日志)

复制代码
local t = {}
local mt = {
    __newindex = function(tbl, key, value)
        print("尝试设置:", key, "=", value)
        -- 可选择是否真正写入
        rawset(tbl, key, value)  -- 使用 rawset 避免递归
    end
}
setmetatable(t, mt)

t.name = "Alice"  -- 输出: 尝试设置: name = Alice
print(t.name)     -- Alice

示例 2:__newindex 是表(常用于"写入重定向")

复制代码
local storage = {}
local proxy = {}
setmetatable(proxy, { __newindex = storage })

proxy.x = 10
proxy.y = 20

print(proxy.x, proxy.y)     -- nil, nil(proxy 本身没写入)
print(storage.x, storage.y) -- 10, 20(写入到了 storage)

注意:

  • 如果键已存在,赋值不会触发 __newindex
  • __newindex 函数内直接写 tbl[key] = value 会导致无限递归,应使用 rawset

三、rawgetrawset ------ 绕过元表的直接操作

有时我们希望跳过元表机制 ,直接对表进行读写。这时就需要 rawgetrawset

  • rawget(t, key) → 直接获取 t[key],不触发 __index
  • rawset(t, key, value) → 直接设置 t[key] = value,不触发 __newindex

示例:

复制代码
local t = { x = 1 }
local mt = {
    __index = function() return "from __index" end,
    __newindex = function() error("禁止写入") end
}
setmetatable(t, mt)

print(t.y)           -- "from __index"
print(rawget(t, "y")) -- nil(直接读,不触发 __index)

-- t.z = 99         -- 会报错:禁止写入
rawset(t, "z", 99)   -- ✅ 成功,绕过 __newindex
print(t.z)           -- 99

这两个函数在元方法内部操作表时尤其重要,可以避免触发元表导致的递归或副作用。


四、常见使用场景小结

场景 推荐方案
提供默认值或实现继承 __index = 表
拦截读取做日志或计算 __index = 函数
实现只读表 __newindex = 报错函数
数据校验或属性封装 __newindex = 校验函数 + rawset
避免元表干扰或递归 使用 rawget / rawset
在元方法内部安全读写表 必须使用 rawget / rawset

五、注意事项

  1. __index__newindex 只对"不存在的键"生效
  2. __index__newindex 函数中操作表时,务必使用 rawget / rawset,否则可能触发递归。
  3. 元方法不会被继承 ------ 子表必须显式设置自己的元表。
  4. 性能敏感的代码中,频繁触发元方法会有开销,可考虑用 rawget / rawset 优化。

总结

__index__newindexrawgetrawset 是 Lua 元表机制中最基础也最实用的组成部分。它们不复杂,但在构建配置系统、对象模型、数据代理、调试工具时非常有用。

相关推荐
ZTLJQ4 小时前
序列化的艺术:Python JSON处理完全解析
开发语言·python·json
2401_891482174 小时前
多平台UI框架C++开发
开发语言·c++·算法
88号技师4 小时前
2026年3月中科院一区SCI-贝塞尔曲线优化算法Bezier curve-based optimization-附Matlab免费代码
开发语言·算法·matlab·优化算法
t198751284 小时前
三维点云最小二乘拟合MATLAB程序
开发语言·算法·matlab
m0_726965985 小时前
面面面,面面(1)
java·开发语言
2401_831920745 小时前
分布式系统安全通信
开发语言·c++·算法
~无忧花开~5 小时前
React状态管理完全指南
开发语言·前端·javascript·react.js·前端框架
阿贵---6 小时前
C++中的RAII技术深入
开发语言·c++·算法
Traced back6 小时前
怎么用 Modbus 让两个设备互相通信**,包含硬件接线、协议原理、读写步骤,以及 C# 实操示例。
开发语言·c#