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

相关推荐
寻星探路5 小时前
Java EE初阶启程记13---JUC(java.util.concurrent) 的常见类
java·开发语言·java-ee
哲Zheᗜe༘6 小时前
了解学习Python编程之python基础
开发语言·python·学习
落日漫游6 小时前
数据结构笔试核心考点
java·开发语言·算法
疯狂吧小飞牛6 小时前
Lua C API 中的注册表介绍
java·c语言·lua
寻找华年的锦瑟6 小时前
Qt-配置文件(INI/JSON/XML)
开发语言·qt
HY小海7 小时前
【C++】AVL树实现
开发语言·数据结构·c++
workflower7 小时前
Fundamentals of Architectural Styles and patterns
开发语言·算法·django·bug·结对编程
Roc-xb7 小时前
ModuleNotFoundError: No module named ‘conda_token‘
开发语言·python·conda
人工干智能7 小时前
Python 开发中:`.ipynb`(Jupyter Notebook 文件)和 `.py`(Python 脚本文件)
开发语言·python·jupyter