类型内省 |
|
首先,为什么我们需要类型内省?你可能根本不需要使用类型内省。鸭子类型 [2] 和 [多态性] 可以根据类型进行调度,而无需程序员干预。你可能仍然需要检查类型,例如出于调试目的 [3] 或类型检查 (LuaTypeChecking)。你可能还想以特定自定义方式调度代码,例如 [string.gsub] 的行为方式取决于 repl
是字符串、表还是函数。不幸的是,界限有时会模糊,例如 FuncTables,它们是带有调用运算符的表,允许它们像函数一样工作。此外,你可能想定义一个函数,该函数从字符串或由文件名指定的 文件加载数据,但两者都是字符串,因此你需要其他方法来区分它们(例如,使用两个单独的函数,如 [loadstring] 和 [loadfile])。
[type] 函数以字符串形式返回值的基类型。Lua 中只有八种基本类型("nil"
、"number"
、"string"
、"boolean"
、"table"
、"function"
、"thread"
或 "userdata"
)。
C API 中的 [lua_type] 函数类似于 type
,但返回一个整数常量。它还区分两种用户数据类型:LUA_TUSERDATA 和 LUA_TLIGHTUSERDATA(参见 LightUserData)。还有一个 lua_typename
函数,它将此整数转换回 type
返回的字符串形式。
[io.type] 函数确定给定对象是否为文件句柄(以及它是否已打开或关闭)。许多用户库遵循类似的模式,通过提供自己的函数来检查给定对象是否为库定义的类型,该函数要么返回 true/false
,要么返回类名字符串。此函数使用下面描述的方法实现。但是,这种风格可能会违反鸭子类型,并可能阻止 (LuaVirtualization)。
相等运算符,更准确地说,是 [rawequal] 函数,可以检查两个值是否具有相同的标识(这意味着它们是相同的对象,因为对象具有唯一的标识)。如果一个类中的所有对象都作为键存储在一个表中,那么可以通过索引表来检查标识(最好是弱键表,尽管在 Lua 5.2 之前,这有一个警告——GarbageCollectingWeakTables)。
local isfoo_ = setmetatable({}, {__mode='k'}) function newfoo() local self = {} isfoo_[self] = true return self end function isfoo(o) return isfoo_[o] end
如果一个类中的所有对象,并且只有这些对象,都具有相同的元表(或由 __metatable
元方法返回的值),那么可以检查该元表。
local foo_mt = {} function newfoo() local self = setmetatable({}, foo_mt) return self end function isfoo(o) return getmetatable(o) == foo_mt end
要获取用户定义类型的名称作为字符串,可以先获取对象的类型,然后从类型中获取名称。可以通过对类型使用 tostring
来实现,但有些人认为 tostring
仅用于调试。或者,可以在类型中甚至在对象本身中存储一个 _NAME
字段 [9][4] (TypeOf)。选择此字段名称的原因是,模块的 _NAME
字段由 [module] 函数设置,而模块通常用作类型,即使那些不使用模块函数 (LuaModuleFunctionCritiqued) 的人,也可能仍然遵循使用 _NAME
的约定。不幸的是,索引对象可能会引发错误,而不是返回 nil
(鸭子类型也会出现此问题)。
有些人修改了 type
函数以支持用户定义类型,可能是通过查询 __type
元方法 [5][6]。这可能会破坏现有代码,这些代码期望只返回基本类型,或者在全局范围内增加一些开销。但是,可以在本地范围内替换自定义的 type
函数:local type = mytype
。其他人让用户定义类型(或子类型)仅作为 type
的第二个返回值返回 [7][8]。
一些面向对象框架(面向对象编程)实现了它们自己的内省功能,但这些功能可能与不是用该框架创建的对象不兼容。
并非所有这些类型内省方法都能完全防止有意滥用(参见 沙箱)。然而,在正常的 Lua 使用中,对象的标识是无法伪造的。此外,通过限制对调试库的访问并提供 __metatable
元方法,可以防止公开访问对象的元表,以及公开元表标识。这些可以阻止来自不受信任代码的滥用。
注意:对于一个相对简单的函数,它允许您在 Lua 崩溃的情况下打印不同的对象,请参见 IntrospectionFunctionsLua。