【问题标题】:Declaring variables and scope question for Lua为 Lua 声明变量和范围问题
【发布时间】:2010-11-04 03:01:09
【问题描述】:

我是Bitfighter 的首席开发人员,我们使用 Lua 作为脚本语言,让玩家可以编写自己的自定义机器人飞船。

在 Lua 中,你不需要声明变量,所有变量默认为全局范围,除非另有声明。这会导致一些问题。以下面的sn-p为例:

loc = bot:getLoc()
items = bot:findItems(ShipType)     -- Find a Ship

minDist = 999999
found = false

for indx, item in ipairs(items) do           
   local d = loc:distSquared(item:getLoc())  

   if(d < minDist) then
      closestItem = item
      minDist = d
   end
end

if(closestItem != nil) then 
   firingAngle = getFiringSolution(closestItem) 
end

在这个 sn-p 中,如果 findItems() 没有返回任何候选者,那么最近的Item 仍将引用它最后一次找到的任何船只,并且在此期间,该船只可能已经被杀死。如果这艘船被杀死,它就不再存在,getFiringSolution() 将失败。

你发现问题了吗?好吧,我的用户也不会。它很微妙,但具有戏剧性的效果。

一种解决方案是要求声明所有变量,并将所有变量默认为本地范围。虽然这种变化不会使程序员无法引用不再存在的对象,但它会使无意中这样做变得更加困难。

有没有办法告诉 Lua 将所有变量默认为本地范围,和/或要求它们被声明?我知道其他一些语言(例如 Perl)有这个选项可用。

谢谢!


这里有很多好的答案,谢谢!

我决定使用 Lua 'strict' 模块的略微修改版本。这似乎让我到达了我想去的地方,我会稍微改进一下以改进消息并使它们更适合我的特定环境。

【问题讨论】:

  • 顺便说一句,您是否有充分的理由在 if 语句中添加额外的大括号?
  • 好吧,在你提出这个问题之前,我会回答他们是必需的。但现在我必须回答,省略括号对我来说是错误的!
  • 当然是个人喜好。但重要的是要记住 Lua 不是 C 或 Pascal 之类的。 Lua 就是 Lua,要有效地使用它,你必须按原样使用它,而不是让它成为其他编程语言的糟糕替代品。我发现这种“语法看起来错误”的事情确实有助于将 Lua 放入我的大脑中,这与 C++ 和我所知道的其他语言不同。总之,我的意见是:如果是用Lua写的,就用Lua的方式写吧! :-)
  • 是的,你提出了一些好观点。 Lua 不是 C,但我认为某些 C 约定使代码更具可读性(至少对我而言:-)。我已经克服了早先使用分号结束行的习惯,并且在使用 C++ 和 Lua 时,我确实尝试让我的格式样式有点不同。但我可能会在条件子句中坚持使用括号一段时间。
  • 所以,Alex,如果我理解你的话,如果 Lua 允许多种方式来编写某些东西,那么与其他语言形式不相似的方式将优于与其他语言形式相似的方式?如果是这样,那么我们必须努力编写对非 Lua 编码人员尽可能不透明的 Lua 代码。

标签: lua scope


【解决方案1】:

没有设置此行为的选项,但标准安装提供了一个“严格”模块,它正是这样做的(通过修改元表)。 用法: 要求“严格”

如需更深入的信息和其他解决方案:http://lua-users.org/wiki/DetectingUndefinedVariables,但我建议使用“严格”。

【讨论】:

  • 这似乎是最简单的答案,也是我一直在寻找的。我想更好地理解这里建议的其他解决方案,看看使用“严格”是否有任何缺点。
【解决方案2】:

排序。

在 Lua 中,全局变量名义上存在于全局变量表 _G 中(实际情况要复杂一些,但从 Lua 方面来看,无法告诉 AFAIK)。与所有其他 Lua 表一样,可以将 __newindex 元表附加到 _G 以控制如何将变量添加到其中。当有人创建全局变量时,让这个 __newindex 处理程序做任何你想做的事情:抛出错误,允许它但打印一个警告,等等。

要干预_G,使用setfenv 是最简单和最干净的。请参阅documentation

【讨论】:

    【解决方案3】:

    “本地默认是错误”。请看

    http://lua-users.org/wiki/LocalByDefault

    http://lua-users.org/wiki/LuaScopingDiscussion

    您需要使用某种全球环境保护措施。有一些静态工具可以做到这一点(不太成熟),但最常见的解决方案是使用运行时保护,基于_G 的元表中的__index__newindex

    耻辱插件:这个页面也可能有用:

    http://code.google.com/p/lua-alchemy/wiki/LuaGlobalEnvironmentProtection

    请注意,虽然它讨论了嵌入到 swf 中的 Lua,但所描述的技术(参见 sources)确实适用于通用 Lua。我们在工作中的生产代码中使用了这些方面的内容。

    【讨论】:

    • 默认情况下本地是(可以说)错误的,但默认情况下也是全局的!我查看了您指向的代码,它看起来与使用上面建议的“严格”模块非常相似。它们有什么显着不同吗?
    • 默认全局也是错误的,但它是 Lua 遗留的,几乎无法更改(在 Lua 中编写 mini-DSL 时也非常有用)。
    • 主要区别(在那些严格的模块之间)是我试图强制明确和简洁地声明全局变量(因为我认为它们是在我的代码中要摆脱的邪恶事物 - 包括全局职能)。 Lua 的 etc/strict.lua 不太明确。但这是个人喜好。
    【解决方案4】:

    实际上,带有对船的陈旧引用的额外全局变量足以防止 GC 丢弃该对象。因此,它可以在运行时通过注意到这艘船现在“死亡”并拒绝对它做任何事情来检测到。它仍然不是正确的船,但至少你不会坠毁。

    您可以做的一件事是将用户脚本保存在sandbox 中,可能每个脚本都有一个沙箱。通过正确操作沙箱的环境表或其元表,您可以安排在调用用户代码之前(或之后)丢弃沙箱中的所有或大部分全局变量。

    调用后清理沙盒的好处是可以丢弃对不应该闲逛的东西的额外引用。这可以通过保留允许保留在环境中的字段的白名单并删除所有其余字段来完成。

    例如,以下实现了对用户提供的函数的沙盒调用,其环境仅包含为每次调用提供的新暂存表后面的白名单名称。

    -- 可供用户脚本使用的全局变量表 本地用户_G = { 打印=_G.打印, 数学=_G.math, -- ... } -- 用户沙箱的元表 本地 env_mt = { __index=user_G } -- 在一个沙箱中调用函数,在这个环境中新的全局 -- 变量可以被创建和修改,但是当 -- 用户代码完成。 函数 doUserCode(user_code, ...) local env = setmetatable({}, env_mt) -- 使用 RO 全局变量创建一个全新的用户环境 setfenv(user_code, env) -- 挂在用户代码上 本地结果 = {pcall(user_code, ...)} setfenv(user_code,{}) 返回解包(结果) 结尾

    如果您愿意,可以通过将全局表推回到另一个元表访问之后,将其扩展为只读。

    请注意,完整的沙盒解决方案还会考虑如何处理意外(或恶意)执行无限(或仅非常长)循环或其他操作的用户代码。对此的通用解决方案是Lua list 上偶尔讨论的话题,但好的解决方案很难。

    【讨论】:

    • 引用死船对象的问题是对象的生命周期是由主C++程序控制的,所以可以在没有Lua同意或控制的情况下删除该对象。 Lua 有一个 userdata 指针的引用,它突然指向了一个无效的对象。我怎样才能检测到呢?我需要更好地处理这个问题,并且愿意接受任何建议。我正在按照沙盒的方式工作,但我确实希望允许创建全局变量,只要它们是有意和故意创建的。以上所有建议似乎都可以做到这一点。
    猜你喜欢
    • 2013-11-19
    • 2022-01-21
    • 2012-06-11
    • 2020-03-18
    • 2014-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多