Lua:检查方法的主题

obj = {}

function obj:setName(name)
    print("obj: ", self)
    print("name: ", obj)
end

我创建了一个对象并分配了一个以上述方式的方法。现在我这样调用它:

obj:setName("blabla")

then关键字会引用obj。我的问题是函数也有可能通过以下方式访问:

obj.setName("blabla")

在这种情况下,obj不会作为参数传递,“blabla”将代替self参数,而不是作为名称使用。这是因为函数声明中的:运算符仅仅是一个简写/语法糖。

function obj.setName(self, name)

我能否以某种方式正确检查self是否确实是主体/函数是否由冒号运行?argCount无法告知,也无法在函数内部直接写obj,因为它将被实例化,函数被引用在绝非定义它的作用域之外。我的唯一想法是检查self是否具有成员“setName”。

function obj:setName(name)
    if ((type(self) ~= "table") or (self.setName == nil)) then
        print("no subject passed")

        return
    end

    print("obj: ", self)
    print("name: ", obj)
end

但这也不干净。

编辑:现在这样做:

local function checkMethodCaller()
    local caller = debug.getinfo(2)

    local selfVar, self = debug.getlocal(2, 1)

    assert(self[caller.name] == caller.func, [[try to call function ]]..caller.name..[[ with invalid subject, check for correct operator (use : instead of .)]])
end

function obj:setName(name)
    checkMethodCaller()

    print(self, name)
end
点赞
用户2198692
用户2198692

你可以将元表分配给对象,在 setName 方法中仅仅检查 self 的元表是否正确:

obj = {}
local objmt = {}

setmetatable(obj, objmt)

function obj:setName(name)
    if getmetatable(self) ~= objmt then
        error("Panic, wrong self!")  -- 或者您可以更优雅地处理它
    end
    self.name = name
end

编辑:

当然,如果有人故意替换或删除元表,则会完全破坏函数。

2013-05-19 20:18:38
用户1244588
用户1244588

通常在脚本中,文档比类型检查更为重要。如果你检查所有内容,最终会看到一些性能影响。首先应该添加良好的函数文档头。

话虽如此,以下选项值得考虑:

通常仅对 self-argument 进行类型检查就足以防止类型错误,因为 name 通常是一个字符串,所以如果你意外地输入了 obj.setName("Foo"),那么 self 的类型就是一个字符串而不是一个表格。

-- 检查参数类型
function obj:setName(name)
   assert(type(self) == "table");
   -- more code
end

你当然也可以使用参数的数量。请注意,我使用 >= 2 而不是 == 2,这很有用,因为如果你链接了一些调用,比如 obj:setName(foo:getInfo()),附加的返回值会破坏执行,即使你的 name 参数可能是正确的值。

-- 检查参数数量
function obj.setName(...)
   assert(select('#', ...) >= 2);

   local self, name = ...;
   -- more code
end

下面的变体甚至更进一步,它不仅保证你的 self 是一个表格并且包含正确的函数,而且 self 是 _同一个表格_。这是因为在 Lua 中,比较表格并不比较它们的内容,而是比较它们的表格 ID,这是唯一的。

然而,这也需要为每个方法和实例化对象创建一个闭包,这是一些负担。

-- 对象表格比较
function Construct()
   local o = {};

   function o:setName(name)
      assert(self == o);
      -- more code
   end

   返回 o;
end

最后一个我想到的变体(主要是因为我写过一些非常相似的代码)是通过构造函数跟踪你的对象,同时使用一个原型。

local Objects = setmetatable({}, { __mode = "k" });
-- __mode 只是让 GC 忽略这个表格,以便如果不再使用,GC 可以收集你的对象。
-- 如果没有元表,它将会是一个内存泄漏。
local Prototype = {
   setName = function(self, name)
      assert(IsMyObject(self));
      -- more code
   end
}

function IsMyObject(self)
   return not not Objects[self];
end

function ConstructMyObject()
   local o = {};
   Objects[o] = { __index = Prototype };
   return setmetatable(o, Objects[o]);
end

明显的好处是你的方法不再是每个对象的单个闭包。然而,这种方式也可以轻松地实现其他功能,比如使你的对象不可变,或实现基本继承。

2013-05-21 14:52:21