Lua 之Module介绍

包管理库提供了从 Lua 中加载模块的基础库。 只有一个导出函数直接放在全局环境中: [require]。 所有其它的部分都导出在表 package 中。

require (modname)

加载一个模块。 这个函数首先查找 [package.loaded] 表, 检测 modname 是否被加载过。 如果被加载过,require 返回 package.loaded[modname] 中保存的值。 否则,它会为模块寻找加载器。

require 遵循 [package.searchers] 序列的指引来查找加载器。如果改变这个序列,我们可以改变 require 如何查找一个模块。 下列说明基于 [package.searchers]的默认配置。

首先 require 查找 package.preload[modname] 。 如果这里有一个值,这个值(必须是一个函数)就是那个加载器。 否则 require 使用 Lua 加载器去查找 [package.path]的路径。 如果查找失败,接着使用 C 加载器去查找 [package.cpath]的路径。 如果都失败了,再尝试一体化 加载器 。

每次找到一个加载器,require 都用两个参数调用加载器: modname 和一个在获取加载器过程中得到的参数。 (如果通过查找文件得到的加载器,这个额外参数是文件名。) 如果加载器返回非空值, require 将这个值赋给 package.loaded[modname]。 如果加载器没能返回一个非空值用于赋给 package.loaded[modname]require 会在那里设入 true 。 无论是什么情况,require 都会返回 package.loaded[modname] 的最终值。

如果在加载或运行模块时有错误, 或是无法为模块找到加载器, require 都会抛出错误。

我们先看一下在lua文件中不显示require,lua运行环境会默认加载哪些, 可以通过遍历package.loaded数组来查看。

print("Before the require function , packages in the package.loaded :")
for k ,v in pairs(package.loaded) do
print(k,v)
end
Before the require function , packages in the package.loaded :
os table: 0x7ffc52403f00
table table: 0x7ffc524038e0
math table: 0x7ffc524054a0
package table: 0x7ffc524034a0
_G table: 0x7ffc524029b0
coroutine table: 0x7ffc52403fe0
bit32 table: 0x7ffc52403d60
utf8 table: 0x7ffc52405980
string table: 0x7ffc524051f0
debug table: 0x7ffc52404db0
io table: 0x7ffc52404490

如何通过require 来呼叫外部lua 文件

首先,创建一个moduleB.lua,内容如下

Jason={}
function Jason.Sum(max)
sum=0
for i=0,max,2 do --这个for循环用法是->i 以2的增长方式递增到max
sum=sum+i
end
return sum
end

其次,创建moduleA.lua

-- package.path = "/Users/jason/Desktop/reqtest/moduleB.lua"
package.path = "./moduleB.lua" require"moduleB.lua" for k,v in pairs (package.loaded) do
print (k,v)
end print (package.loaded["moduleB.lua"])
print(Jason.Sum(100))
print(package.path)
print(package.cpath)
输出为:
debug table: 0x7fd9bec04db0
io table: 0x7fd9bec04490
string table: 0x7fd9bec051f0
moduleB.lua true
math table: 0x7fd9bec054a0
bit32 table: 0x7fd9bec03d60
package table: 0x7fd9bec034a0
coroutine table: 0x7fd9bec03fe0
table table: 0x7fd9bec038e0
_G table: 0x7fd9bec029b0
utf8 table: 0x7fd9bec05980
os table: 0x7fd9bec03f00
true
2550
./moduleB.lua
/usr/local/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/loadall.so;./?.so

可以看到,在require相应的module后,package load会将其加载进来 并存储为true,我们可以利用这一点做文件load的check

dofile()

按参数filename提供的文件名打开一个文件并将其内容作为一个Lua程序块执行,当省略参数fielname时,函数默认把标准输入的内容作为程序块执行,执行结束后函数会把程序块返回的所有值作为函数的返回值返回,如果执行过程中发生了错误,函数会将错误向上跑出给它的调用者(当函数dofile()不是运行在保护模式的状态下)。

用法是直接呼叫文件名,注意路径位置

dofile("./hellow.lua")

package

包是一种组织代码的方式。

使用表实现packages的明显的好处是:我们可以像其他表一样使用packages,并且可以使用语言提供的所有的功能,带来很多便利。大多数语言中,packages不是第一类值(first-class values)(也就是说,他们不能存储在变量里,不能作为函数参数。。。)因此,这些语言需要特殊的方法和技巧才能实现类似的功能。Lua中,虽然我们一直都用表来实现package,但也有其他不同的方法可以实现package.

例一

vector3d = {}  -- 包名
function vector3d.function1()
......
end
function vector3d.function2()
......
if (vector3d.function1()) then
......
end
end
return vector3d

这样定义的就是一个vector3d包,使用require语言打开这个包后,就可以使用 vector3d.function1和vector3d.function2这两个函数了。

这是最直接最好理解的一种Package定义方式,但是有一定的弊端。这个弊端主要体现在Package的实现过程中。可以看到,即使在

vector3d.function2()中使用function1()函数,也必须完整的加上vector3d包名,否则无法进行函数调用。

特别的注意最后的 return vector3d 语句,有了这句后调用者可以按照如下方式重命名包:

MyPackage =  require "vector3d"
MyPackage.function2()

例二:使用局部函数定义所有的Package内函数,然后在Package的结尾处将需要公开的函数直接放入Package中。代码像这样:

vector3d = {}  -- 包名
local function function1()
......
end local function function2()
......
if (function1()) then
......
end
end
vector3d = {function1 = functoin1,
function2function2 = function2
}
return vector3d

最后给包中赋值的部分就是将需要的接口公开的部分。这样做的好处:不需要公开的函数可以完全隐藏起来(都是local函数);Package内部的各个函数相互之间调用的时候不再需要加Package名称进行区分; 可以按照需要随意的重命名Package公开的接口名称。

可以用local N = {}来保存数据和定义私有变量和函数。能明确的区分出接口和私有的定义,公开接口的名称还可以随意改变,这就意味着可以随意替换内部实现而不需要影响外部调用者。

package 相关函数介绍

  1. package.config

    一个描述有一些为包管理准备的编译期配置信息的串。 这个字符串由一系列行构成:

  • 第一行是目录分割串。 对于 Windows 默认是 '\' ,对于其它系统是 '/' 。
  • 第二行是用于路径中的分割符。默认值是 ';' 。
  • 第三行是用于标记模板替换点的字符串。 默认是 '?' 。
  • 第四行是在 Windows 中将被替换成执行程序所在目录的路径的字符串。 默认是 '!' 。
  • 第五行是一个记号,该记号之后的所有文本将在构建 luaopen_ 函数名时被忽略掉。 默认是 '-'。
  1. package.cpath

    这个路径被 [require] 在 C 加载器中做搜索时用到。

    Lua 用和初始化 Lua 路径 [package.path]相同的方式初始化 C 路径 [package.cpath] 。 它会使用环境变量 LUA_CPATH_5_3 或 环境变量 LUA_CPATH 初始化。 要么就采用 luaconf.h 中定义的默认路径。

  2. package.loaded

    用于 [require] 控制哪些模块已经被加载的表。 当你请求一个 modname 模块,且 package.loaded[modname] 不为假时, [require]简单返回储存在内的值。

    这个变量仅仅是对真正那张表的引用; 改变这个值并不会改变 [require 使用的表。

  3. package.loadlib(libname,funcname)

    让宿主程序动态链接 C 库 libname

    funcname 为 "*", 它仅仅连接该库,让库中的符号都导出给其它动态链接库使用。 否则,它查找库中的函数 funcname ,以 C 函数的形式返回这个函数。 因此,funcname 必须遵循原型 [lua_CFunction]

    这是一个低阶函数。 它完全绕过了包模块系统。 和 [require]不同, 它不会做任何路径查询,也不会自动加扩展名。 libname 必须是一个 C 库需要的完整的文件名,如果有必要,需要提供路径和扩展名。 funcname 必须是 C 库需要的准确名字 (这取决于使用的 C 编译器和链接器)。

    这个函数在标准 C 中不支持。 因此,它只在部分平台有效 ( Windows ,Linux ,Mac OS X, Solaris, BSD, 加上支持 dlfcn 标准的 Unix 系统)。

  4. package.path

    这个路径被 [require] 在 Lua 加载器中做搜索时用到。

    在启动时,Lua 用环境变量 LUA_PATH_5_3 或环境变量 LUA_PATH 来初始化这个变量。 或采用 luaconf.h 中的默认路径。 环境变量中出现的所有 ";;" 都会被替换成默认路径。

  5. package.preload

    保存有一些特殊模块的加载器,这个变量仅仅是对真正那张表的引用; 改变这个值并不会改变 [require] 使用的表

  6. package.serachers

    用于 [require]控制如何加载模块的表。

    这张表内的每一项都是一个 查找器函数。 当查找一个模块时, [require]按次序调用这些查找器, 并传入模块名([require]的参数)作为唯一的一个参数。 此函数可以返回另一个函数(模块的 加载器)加上另一个将传递给这个加载器的参数。 或是返回一个描述为何没有找到这个模块的字符串 (或是返回 nil 什么也不想说)。

    Lua 用四个查找器函数初始化这张表。

    第一个查找器就是简单的在 [package.preload]表中查找加载器。

    第二个查找器用于查找 Lua 库的加载库。 它使用储存在 [package.path] 中的路径来做查找工作。 查找过程和函数 [package.searchpath描述的一致。

    第三个查找器用于查找 C 库的加载库。 它使用储存在 [package.cpath]中的路径来做查找工作。 同样, 查找过程和函数 [package.searchpath]描述的一致。 例如,如果 C 路径是这样一个字符串

         "./?.so;./?.dll;/usr/local/?/init.so"

    查找器查找模块 foo 会依次尝试打开文件 ./foo.so./foo.dll, 以及 /usr/local/foo/init.so。 一旦它找到一个 C 库, 查找器首先使用动态链接机制连接该库。 然后尝试在该库中找到可以用作加载器的 C 函数。 这个 C 函数的名字是 "luaopen_" 紧接模块名的字符串, 其中字符串中所有的下划线都会被替换成点。 此外,如果模块名中有横线, 横线后面的部分(包括横线)都被去掉。 例如,如果模块名为 a.b.c-v2.1, 函数名就是 luaopen_a_b_c

    第四个搜索器是 一体化加载器。 它从 C 路径中查找指定模块的根名字。 例如,当请求 a.b.c 时, 它将查找 a 这个 C 库。 如果找得到,它会在里面找子模块的加载函数。 在我们的例子中,就是找 luaopen_a_b_c。 利用这个机制,可以把若干 C 子模块打包进单个库。 每个子模块都可以有原本的加载函数名。

    除了第一个(预加载)搜索器外,每个搜索器都会返回 它找到的模块的文件名。 这和 [package.searchpath的返回值一样。 第一个搜索器没有返回值。

  7. package.searchpath (name, path [, sep [, rep]])

    在指定 path 中搜索指定的 name

    路径是一个包含有一系列以分号分割的 模板 构成的字符串。 对于每个模板,都会用 name 替换其中的每个问号(如果有的话)。 且将其中的 sep (默认是点)替换为 rep (默认是系统的目录分割符)。 然后尝试打开这个文件名。

    例如,如果路径是字符串

         "./?.lua;./?.lc;/usr/local/?/init.lua"

    搜索 foo.a 这个名字将 依次尝试打开文件 ./foo/a.lua , ./foo/a.lc ,以及 /usr/local/foo/a/init.lua

    返回第一个可以用读模式打开(并马上关闭该文件)的文件的名字。 如果不存在这样的文件,返回 nil 加上错误消息。 (这条错误消息列出了所有尝试打开的文件名。)

定义Module的方式

定义module有两种方式,旧的方式,适用于Lua 5.0以及早期的5.1版本,新的方式现在均支持。

旧的方式:

通过module("...", package.seeall)来显示声明一个包

--定义:

-- oldmodule.lua
module("oldmodule", package.seeall)
function foo()
print("oldmodule.foo called")
end
--使用:

require "oldmodule"
oldmodule.foo()
  • 1.module() 第一个参数就是模块名,如果不设置,缺省使用文件名。

  • 2.第二个参数package.seeall,默认在定义了一个module()之后,前面定义的全局变量就都不可用了,包括print函数等,如果要让之前的全局变量可见,必须在定义module的时候加上参数package.seeall。 具体参考云风这篇文章

  • package.seeall(module)功能:为module设置一个元表,此元表的__index字段的值为全局环境_G。所以module可以访问全局环境.

之所以不再推荐module("...", package.seeall)这种方式,官方给出了两个原因。

  • 1.package.seeall这种方式破坏了模块的高内聚,原本引入oldmodule只想调用它的foo()函数,但是它却可以读写全局属性,例如oldmodule.os.

  • 2.第二个缺陷是module函数的side-effect引起的,它会污染全局环境变量。module("hello.world")会创建一个hello的table,并将这个table注入全局环境变量中,这样使得不想引用它的模块也能调用hello模块的方法。

新的方式: 通过return table来实现一个模块

--newmodule.lua
local newmodule = {}
function newmodule.foo()
print("newmodule.foo called")
end
return newmodule

使用

local new = require "newmodule"
new.foo()

因为没有了全局变量和module关键字,引用的时候必须把模块指定给一个变量

Lua 学习之基础篇七<Lua Module,Package介绍>的更多相关文章

  1. Lua 学习之基础篇四<Lua table(表)>

    table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组.字典等. Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil. Lua ta ...

  2. Lua 学习之基础篇十<Lua 常见的语法规则>

    下面讲一些lua 常见的用法和规则,可以为学习理解lua带来帮助,最后附上的部分是lua的基本操作,基本包含所有常用语法语句. 1. if判断 lua把 nil 和false 视为"假&qu ...

  3. Lua 学习之基础篇一<Lua 运算符>

    引言 由于刚接触lua,个人觉得接触一门新语言,就要一定要对基础的部分做一个快速了解. 于是参考网上相关资料吸收并整理下来作为笔记,模糊的时候用来回顾一下. 这些部分基本都是经过自己手动测试梳理过,没 ...

  4. Lua 学习之基础篇九<Lua 协同程序(Coroutine)>

    引言 讲到协程,首先来介绍一下线程和协程的区别 lua协程和多线程 相同之处:拥有自己独立的桟.局部变量和PC计数器,同时又与其他协程共享全局变量和其他大部分东西 不同之处:一个多线程程序可以同时运行 ...

  5. Lua 学习之基础篇八<Lua 元表(Metatabble)&&继承>

    讲到元表,先看一段table的合并动作. t1 = {1,2} t2 = {3,4} t3 = t1 + t2 attempt to perform arithmetic on a table val ...

  6. Lua 学习之基础篇六<Lua IO 库>

    引言 I/O 库提供了两套不同风格的文件处理接口. 第一种风格使用隐式的文件句柄: 它提供设置默认输入文件及默认输出文件的操作, 所有的输入输出操作都针对这些默认文件. 第二种风格使用显式的文件句柄. ...

  7. Lua 学习之基础篇五<Lua OS 库>

    lua os库提供了简单的跟操作系统有关的功能 1.os.clock() 返回程序所运行使用的时间 local nowTime = os.clock() print("now time is ...

  8. Lua 学习之基础篇三<Lua 字符串操作>

    Lua字符串可以使用以下三种方式表示: 单引号间的一串字符. 双引号间的一串字符. [[和]]间的一串字符. string = [["Lua"]] print("字符串 ...

  9. Lua 学习之基础篇二<Lua 数据类型以及函数库 汇总>

    引言 前面讲了运算符,这里主要对Lua的数据处理相关的数据类型和函数库进行总结归纳,后面会再接着单独分开讲解具体使用. 首先因为Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值. 值可以存储 ...

随机推荐

  1. yarn和npm

    Yarn和npm命令对比 npm install === yarn npm install taco --save === yarn add taco npm uninstall taco --sav ...

  2. HttpClient的几种请求方式

    public static String doPostToken(String tokenUrl,String clientId,String clientSecret,String grantTyp ...

  3. Java语言资源国际化步骤

    语言资源国际化步骤:   1. 定义资源文件(如:language),需要使用命令native2ascii命令进行转码:(native2ascii是jdk中的转码工具,在jdk的bin目录下)   2 ...

  4. 利用Python进行数据分析_Pandas_数据结构

    申明:本系列文章是自己在学习<利用Python进行数据分析>这本书的过程中,为了方便后期自己巩固知识而整理. 首先,需要导入pandas库的Series和DataFrame In [21] ...

  5. Linux (x86) Exploit 开发系列教程之七 绕过 ASLR -- 第二部分

    (1)原理: 使用爆破技巧,来绕过共享库地址随机化.爆破:攻击者选择特定的 Libc 基址,并持续攻击程序直到成功.这个技巧是用于绕过 ASLR 的最简单的技巧. (2)漏洞代码 //vuln.c # ...

  6. 官网实例详解-目录和实例简介-keras学习笔记四

    官网实例详解-目录和实例简介-keras学习笔记四 2018-06-11 10:36:18 wyx100 阅读数 4193更多 分类专栏: 人工智能 python 深度学习 keras   版权声明: ...

  7. ggalluvial|TCGA临床数据绘制桑基图(Sankey)

    本文首发于”生信补给站“,https://mp.weixin.qq.com/s/yhMgkST-rVD6SaQS7R-eoA 桑基图(Sankey diagram),是一种特定类型的流程图,图中延伸的 ...

  8. (六)Redis之数据结构之sorted-set

    一.常用方法 Sorted-Set和Set的区别 Sorted-Set中的成员在集合中的位置是有序的 添加元素 获得元素 删除元素 范围查询 1和2和3和4 添加/获得/删除元素/范围查询 packa ...

  9. Windows 10 下 Linux 子系统的安装和使用

    介绍 适用于 Windows 的 Linux 子系统(英语:Windows Subsystem for Linux,简称 WSL)是一个为在 Windows 10 和 Windows Server 2 ...

  10. 可视化利器 TensorBoard

    人工智能的黑盒: TensorBoard 的作用: 1.用TensorFlow保存图的信息到日志中 tfsummary.FileWriter("日志保存路径", sess.grap ...