Lua 标准库- 模块(Modules)

Lua包库为lua提供简易的加载及创建模块的方法,由require、module方法及package表组成

  1、module (name [, ···])

功能:建立一个模块。

  module的处理流程:

module(name, cb1, cb2, ...)

  a. 如果package.loaded[name]是一个table,那么就把这个table作为一个module

b. 如果全局变量name是一个table,就把这个全局变量作为一个module

  c. 当以前两种情况都不存表name时,将新建一个表,并使其作为全局名name的值,并package.loaded[name],而且设t._NAME为name,t._M为module,t._PACKAGE为包的全名(模块名-组件a.b.c);最后把此module设t作为当前函数的新环境表和package.loaded[name]的新值(也就是说,旧的环境表将不能访问,除了加上package.seeall参数外),以被require使用

    即:创建table:t = {[name]=package.loaded[name], ["_NAME"]=name, ["_M"]=t, ["_PACKAGE"]=*name*(删除了最后的".XXXX"部分)}. 如果name是一个以点分割的串,那么得到的mod类似这个样子:

hello.world==> {["hello"]={["world"]={XXXXXXX}}}

   d. 依次调用cbs:
      cb1(mod), cb2(mod),...

e. 将当前模块的环境设置为module,同时把package.loaded[name] = module

  module(name)后的可选参数为接收module名的函数,如package.seeall

  当在模块文件中使用module函数的时候,如下所示;

module “mymodule”  

  实际上等同于以下的语句:

local modname = “mymodule”     -– 定义模块名
local M = {}                   -- 定义用于返回的模块表
_G[modname] = M                -- 将模块表加入到全局变量中
package.loaded[modname] = M    -- 将模块表加入到package.loaded中,防止多次加载
setfenv(1,M)                   -- 将模块表设置为函数的环境表,这使得模块中的所有操作是以在模块表中的,这样定义函数就直接定义在模块表中

  通过module(),可以方便的编写模块中的内容。module 指令运行完后,整个环境被压栈,所以前面全局的东西再看不见了。比如定义了一个 test 模块,使用module("test")后,下面不再看的见前面的全局环境。  如果在这个模块里想调用 print 输出调试信息怎么办呢?一个简单的方法是

local print=print
module("test")

  这样 print 是一个 local 变量,下面也是可见的。或者可以用

local _G=_G
module("test")

  那么 _G.print 也是可以用的。

  当然还有一种巧妙的方式,lua 5.1 提供了一个 package.seeall 可以作为 module 的option 传入module("test",package.seeall)

  这样就 OK 了。至于它们是如何工作的,还是自己读源码会理解的清楚一些。

  具体的使用比如:声明myModule包

-- File :  myModule.lua
module( "myModule", package.seeall) --显示声明一个myModule包

function printMsg(msg)
    print("myModule :" .. msg)
end

function setMag(msg)
    test.msg = msg
end

调用该myModule包:

-- File : myModuleTest.lua
local print = print 

local mod = require("myModule")
print("call myModule start")
test = {}
mod.setMag("test module~")
print(test.msg)
mod.printMsg("yes")

输出结果如下:

  all myModule start
  test module~
  myModule :yes

通过module("...", package.seeall)来显示声明一个包。看很多github上面早期的开源项目使用的都是这种方式,但官方不推荐再使用这种方式。

因为:1, package.seeall这种方式破坏了模块的高内聚,原本引入oldmodule只想调用它的foo()函数,但是它却可以读写全局属性,例如oldmodule.os.
              2, module函数的side-effect引起的,它会污染全局环境变量。
module("hello.world")会创建一个hello的table,并将这个table注入全局环境变量中,这样使得不想引用它的模块也能调用hello模块的方法。

所以还是  通过return table来实现一个模块 的更优。使用如下:

--File : module.lua
local module = {}

function module.foo()
  print("module.foo called")
end

function module:setMsg(msg)
    self.mMsg = msg
end

function module:getMsg()
    return self.mMsg
end

return module
-- File : myModuleTest.lua
local mod = require("module")
mod:setMsg("test module~")
-- mod.setMsg( mod , "test module~") -- 等价上句
print(mod:getMsg())

-- 得到结果如下:test module~

  

  2、require (modname)

  功能:加载指定的模块。

  此函数先检测package.loaded表中是否存在modname,存在则直接返回当中的值,没有则通过定义的加载器加载modname。

1) require机制相关的数据和函数
    package.path:保存加载外部模块(lua中"模块"和"文件"这两个概念的分界比较含糊,因为这个值在不同的时刻会扮演不同的角色)的搜索 路径,这种路径是"模板式的路径",它里面会包含可替代符号"?",这个符号会被替换,然后lua查找这个文件是否存在,如果存在就会调用其中特定的接 口。典型的值为:
    "./?.lua;./?.lc;/usr/local/?/init.lua"
    如果lua代码中调用:require("hello.world")
    那么lua会依次查找:
    ./hello/world.lua ==>这里"hello.world"变成了"hello/world",并替换了模型"./?.lua"
    ./hello/world.lc
    .....
    (这种处理方式和python类似,只不过不需要__init__.py,也有调用python中的__init__.py)
    package.path在虚拟机启动的时候设置,如果存在环境变量LUA_PATH,那么就用该环境变量作为
    它的值,并把这个环境变量中的";;"替换为luaconf.h中定义的默认值,如果不存在该变量就直接使用
    luaconf.h定义的默认值
    
    package.cpath:作用和packag.path一样,但它是用于加载第三方c库的。它的初始值可以通过环境变量
    LUA_CPATH来设置
    package.loadlib(libname, func):相当与手工打开c库libname, 并导出函数func返回,loadlib其实是ll_loadlib

  2) 查找加载器顺序:

   require(在lua中它是ll_require函数)的查找顺序如下:
       a.首先在package.loaded查找modelname,如果该模块已经存在,就直接返回它的值
       b.在package.preload查找modelname, 如果preload存在,那么就把它作为loader,调用loader(L)
       c.根据package.path的模式查找lua库modelname,这个库是通过module函数定义的,对于顶层的lua库,文件名和库名是一 样的而且不需要调用显式地在lua文件中调用module函数(在ll_require函数中可以看到处理方式),也就是说lua会根据lua文件直接完 成一个loader的初始化过程。
       d.根据package.cpath查找c库,这个库是符合lua的一些规范的(export具有一定特征的函数接口),lua先已动态的方式加载该c库,然后在库中查找并调用相应名字的接口,例如:luaopen_hello_world
       e.已第一个"."为分割,将模块名划分为:(main, sub)的形式,根据package.cpath查找main,如果存在,就加载该库并查询相应的接口:luaopen_main_sub,例如:先查找 hello库,并查询luaopen_hello_world接口
       f.得到loder后,用modname作为唯一的参数调用该loader函数。当然参数是通过lua的栈传递的,所以loader的原型必须符合lua的规范:int LUA_FUNC(lua_State *L)
    
       ll_require会将这个loader的返回值符给package.loaded[modelname],如果loader不返回值同时 package.loaded[modelname]不存在时, ll_require就会把package.loaded[modelname]设为true。最后ll_reuqire把package.loaded [modelname]返回给调用者。当加载失败时,require将触发错误

  3) require的另一个功能是避免重复加载同一个文件两次。Lua保留一张所有已经加载的文件的列表(使用table保存)。如果一个加载的文件在表中存在require简单的返回;表中保留加载的文件的虚名,而不是实文件名。所以如果你使用不同的虚文件名require同一个文件两次,将会加载两次该文件。比如require "foo"和require "foo.lua",路径为"?;?.lua"将会加载foo.lua两次。我们也可以通过全局变量_LOADED访问文件名列表,这样我们就可以判断文件是否被加载过;同样我们也可以使用一点小技巧让require加载一个文件两次。比如,require "foo"之后_LOADED["foo"]将不为nil,我们可以将其赋值为nil,require"foo.lua"将会再次加载该文件。

也可以根据自己的需要将package.loaded[modname] or _LOADED[modname]置nil,然后重新require,来完成lua热更新功能。具体的可参见:codedump

  3、package.cpath

  功能:用于require C loader的搜索路径

  可以通过修改LUA_CPATH变量(luaconf.h)修改此值

  4、package.loaded

  功能:一个用于让require知道哪些模块已加载的记录表,如果package.loaded已经有require要的值,则直接返回此值

  5、package.loadlib (libname, funcname)

  功能:通过动态连接C函数库方式加载Lua扩展库

  libname为库文件名,funcname为入口函数(此函数必须为纯C接口函数 c++则需用 extern "C" {} 进行限制)

  6、package.path

  功能:用于require Lua loader的搜索路径

  可以通过修改LUA_PATH变量(luaconf.h)修改此值

  7、package.preload

  功能:一个用于保存特殊模块加载器的表

  8、package.seeall(module)

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

  注:以此函数作为module()的一个选项(详细见module())

参考文章:ClickHere and Here

时间: 2024-10-02 05:56:24

Lua 标准库- 模块(Modules)的相关文章

转 Lua标准库: table函数, 数学函数, 字符串函数/格式化/配对, WoW新增函数, 函数别名

这里只介绍和插件编写比较有关的几个函数. 详细的Lua手册请参照Lua Reference Manual 5.1. assert(value) - 检查一个值是否为非nil, 若不是则(如果在wow.exe打开调试命令)显示对话框以及输出错误调试信息 collectgarbage() - 垃圾收集器. (新增于1.10.1) date(format, time) - 返回当前用户机器上的时间. error("error message",level) - 发生错误时,输出一条定义的错误

Lua标准库(转)

转载地址:http://www.yiibai.com/lua/lua_standard_libraries.html Lua的标准库提供了一组丰富的功能,与C的API直接实现,建立在Lua编程语言函数.Lua编程语言中这些库提供服务,还提供外部服务,如文件和数据库操作. 建于官方的C的API,这些标准库是作为单独的C模块.这包括: 基本库,其中包括协程分库 模块库 字符串操作 表操作 数学库 文件输入和输出 操作系统工具 调试设备 基本库 我们使用的基本库根据遍及不同主题的教程.下表提供了所有相

【python标准库模块四】Json模块和Pickle模块学习

Json模块 原来有个eval函数能能够从字符串中提取出对应的数据类型,比如"{"name":"zhangsan"}",可以提取出一个字典.json模块可一样实现功能,但是更加强大,eval只能识别到python语言的数据结构.json可以识别多语言的数据结构 模块的导入 import json 将数据类型封装成json格式(字符串)用的是dumps dic = {"name":"CodeScrew"} d

python 标准库模块之 os 模块

os 模块 作用 调用系统模块 常见用法 os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径 os.chdir("dirname") 改变当前脚本工作目录:相当于shell下cd os.curdir 返回当前目录: ('.') os.makedirs('dirname1/dirname2') 创建多层目录 os.removedirs('dirname1') 目录为空则删除 os.mkdir('dirname') 生成单级目录:相当于shell中mkdir d

python 标准库模块之 sys 模块

sys 模块的作用 sys 模块用于解释器相关的操作,通常用于脚本传参 sys 模块的常见用法 sys.argv 传递到Python脚本的命令行参数列表,第一个元素是程序本身路径sys.executable 返回python 解释器在当前系统中的绝对路径sys.exit([arg]) 程序中间的退出,arg=0为正常退出sys.platform 返回操作系统平台名称,Linux是linux2,Windows是win32sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量

转:Python标准库(非常经典的各种模块介绍)

Python Standard Library 翻译: Python 江湖群 10/06/07 20:10:08 编译 0.1. 关于本书 0.2. 代码约定 0.3. 关于例子 0.4. 如何联系我们 核心模块 1.1. 介绍 1.2. _ _builtin_ _ 模块 1.3. exceptions 模块 1.4. os 模块 1.5. os.path 模块 1.6. stat 模块 1.7. string 模块 1.8. re 模块 1.9. math 模块 1.10. cmath 模块

haskell模块(modules)

装载模块 Haskell 中的模块是含有一组相关的函数,类型和类型类的组合.而 Haskell 进程的本质便是从主模块中引用其它模块并调用其中的函数来执行操作.这样可以把代码分成多块,只要一个模块足够的独立,它里面的函数便可以被不同的进程反复重用.这就让不同的代码各司其职,提高了代码的健壮性. Haskell 的标准库就是一组模块,每个模块都含有一组功能相近或相关的函数和类型.有处理 List 的模块,有处理并发的模块,也有处理复数的模块,等等.目前为止我们谈及的所有函数,类型以及类型类都是 P

22Python标准库系列之Redis模块

Python标准库系列之Redis模块 What is redis? Redis is an open source (BSD licensed), in-memory data structure store, used as database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, b

1Python标准库系列之模块介绍

Python标准库系列之模块介绍 Python的模块其实就是封装了一个或者多个功能的代码集合,以便于重用,模块可以是一个文件也可以是一个目录,目录的形式称作包. 模块分类 内置模块 内置模块可以理解成当你安装好python环境之后,直接可以使用import导入的就是内置模块,默认模块路径为:C:\Python35\lib,你也可以通过以下方式获取内置模块的路径:  # 导入sys模块  >>> import sys  # 最后一个目录就是内置模块的路径  >>> for