Lua 技术说明 11

重新审视 Require:导入

作者 Wim Couwenberg

此 LTN 依赖于 Lua 5.0 中引入的“loadfile”

摘要

Lua 4.1 引入了“require”函数,该函数加载并运行文件,除非已加载该文件。Lua 5.0 在其基本库中提供 require 作为内置函数。require 命令与 LTN 7“模块和包”一起为 Lua 中的简单模块支持提供了基础。此技术说明提出了一个改进版的 require,称为“import”。提议的 import 方案避免直接访问全局变量,纠正与全局变量相关的安全漏洞,并妥善处理循环模块依赖项。

问题

LTN 7 的模块方法提出,包应在全局变量表中发布其公共接口(包装在表中)。这有以下缺点 require 的当前实现也有一些缺点

解决方案

提议的 import 方案解决了全局包名称全局安全漏洞循环依赖项带来的问题。Import 可以完全在 vanilla Lua 5 中实现。要点

可以使用以下 Lua 5.0 代码实现 import 函数。

local imported = {}

local function package_stub(name)
  local stub = {}
  local stub_meta = {
    __index = function(_, index)
      error(string.format("member `%s' is accessed before package `%s' is fully imported", index, name))
    end,
    __newindex = function(_, index, _)
      error(string.format("member `%s' is assigned a value before package `%s' is fully imported", index, name))
    end,
  }
  setmetatable(stub, stub_meta)
  return stub
end

local function locate(name)
  local path = LUA_PATH
  if type(path) ~= "string" then
    path = os.getenv "LUA_PATH" or "./?.lua"
  end
  for path in string.gfind(path, "[^;]+") do
    path = string.gsub(path, "?", name)
    local chunk = loadfile(path)
    if chunk then return chunk, path end
  end
  return nil, path
end

function import(name)
  local package = imported[name]
  if package then return package end
  local chunk, path = locate(name)
  if not chunk then
    error(string.format("could not locate package `%s' in `%s'", name, path))
  end
  package = package_stub(name)
  imported[name] = package
  setglobals(chunk, getglobals(2))
  chunk = chunk()
  setmetatable(package, nil)
  if type(chunk) == "function" then
    chunk(package, name, path)
  end
  return package
end

import 的典型用法如下

-- import the complex package
local complex = import "complex"

-- complex now holds the public interface
local x = 5 + 3*complex.I

一个包的结构应如下所示

-- first import all other required packages.
local a = import "a"
local b = import "b"

-- then define the package install function.
-- the PIF more or less contains the code of a
-- LTN 7 package.
local function pif(Public, path)

local Private = {}

function Public.fun()
  -- public function
end

-- etc.
end

-- return the package install function
return pif

说明

在加载包之前设置一个“包存根”必须捕获对存根的任何访问(由嵌套导入调用)。为了实现这一点,应将其他导入放在所涉及的每个包的全局作用域中,通常作为第一个调用。请注意,存根(从其访问限制中剥离)稍后将保存包的公共接口。特别是,即使在循环依赖中,也可以安全地引用导入的接口(例如,通过向上值),只要不实际访问接口即可。

导入几乎与 require 向后兼容。但是,导入不会在加载期间定义 _REQUIREDNAME 全局变量。一个不返回 PIF 的“旧式”包仍然会被加载并运行,但导入返回一个空的公共接口。这不会影响旧式代码,因为 require 没有返回值。

以下是两个包相互导入的示例。因为在导入期间没有一个包实际使用另一个包,所以这不会成为问题。

包“a.lua

local b = import "b"

local function pif(pub, name, path)

function pub.show()
  -- use a message from package b
  print("in " .. name .. ": " .. b.message)
end

pub.message = "this is package " .. name .. " at " .. path

end

return pif

包“b.lua

local a = import "a"

local function pif(pub, name, path)

function pub.show()
  -- use a message from package a
  print("in " .. name .. ": " .. a.message)
end

pub.message = "this is package " .. name .. " at " .. path

end

return pif

一些导入和运行两者的代码

local a = import "a"
local b = import "b"

a.show() -- prints "in a: this is package b at ./b.lua"
b.show() -- prints "in b: this is package a at ./a.lua"

弱点

导入函数假定它导入的包是“行为良好”的。当然,一个包仍然可以访问和更新全局变量,因此应小心。没有强制执行包的正确结构(在其全局作用域中导入调用、返回 PIF 等)。

结论

require 函数已被证明非常有用。提议的导入方案建立在这个成功之上。它提供了更受控的包可见性,并在可能的情况下支持循环依赖。导入功能很轻量,并且可以在纯 Lua 5 中完全定义。


最后更新时间:2003 年 2 月 19 日星期三 09:25:05 美国东部时间