Python 和 Lua 学习比较 五

模块

前言

不管是python或者lua,我们打开命令窗口,然后赋值一些变量比如a=1,之后关闭窗口然后再打开,打印a的时候我们得到的是未赋值。
如果我们想要长久的程序,我们就需要写脚本。为了便于维护,我们可能需要写不同的脚本文件。
这时候我们写的脚本文件也可以称之为模块了。文件中的函数我们也可以引用进来并使用。
python文件使用.py做后缀。在模块中,__name__指代我们的模块名。
lua文件使用.lua做后缀。在lua脚本中,...代表一些我们脚本参数

python模块申明和使用

我的平台是windows,所以我先打开命令窗口,先进入我的测试目录cd E:\script\python,E:。然后编写我的脚本文件fibo.py
这里写图片描述

fibo.py内容:

#注释

#Fibonacci number
def fib(n):
    print(__name__)
    a,b = 0,1
    while b<n:
        print(b,end=' ')
        a,b = b,a+b
    print()

def fib2(n):
    result = []
    a,b = 0,1
    while b<n:
        result.append(b)
        a,b = b,a+b
    return result

现在我们引入这个模块:

>>> import fibo #注意这里不要加后缀。
>>> fibo.fib(3)
fibo
1 1 2
>>> fibo.__name__
'fibo'
>>> fib2 = fibo.fib2 #赋值给本地变量,方便访问
>>> fib2(80)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

python模块可以包含一个可执行语句,在引入的时候初始化,并且只执行一次。
每个模块都有自己的标识表,我们访问模块的函数或者变量时:modname.itemname

模块还能引入别的模块,但是不一定要使用import 语句引入模块。
还可以这样: from...import...引入指定名字

>>> from fibo import fib, fib2
>>> fib(400)
1 1 2 3 5 8 13 21 34 55 89 144 233 

上面这个语法将不会引入模块,而是直接引入模块函数,所以上面 fibo是非法的。

还可以:

>>> from fibo import *
>>> fib(400)
1 1 2 3 5 8 13 21 34 55 89 144 233

上面这个方法引入模块所有的名字,除了下划线开头的名字;当然fibo模块名也是非法的。在通常情况下,我们不会用这个方法引入模块,因为这将导致我们引入一些我们不需要的名字,或者将我们已经定义的名字覆盖。

出于性能考虑,对每个模块,python解释器只引入一次;如果你在引用后做了修改,需要重启python解释器或者使用imp.reload():import imp; imp.reload(modulename).
重新加载只能是模块名(必须已经成功加载的),不能重载函数。

值得一提的是,我们import模块的时候,python解释器其实是把我们的脚本编译了一遍,并放在当前目录的__pycache__
这里写图片描述

我们也这样可以执行脚本:python fibo.py <arguments>,arguments代表参数的意思。
现在我们的fibo.py是这样:

#注释

#Fibonacci number
def fib(n):
    print(__name__,1,2)
    a,b = 0,1
    while b<n:
        print(b,end=' ')
        a,b = b,a+b
    print()

def fib2(n):
    result = []
    a,b = 0,1
    while b<n:
        result.append(b)
        a,b = b,a+b
    return result

def _add(a,b): # 这个函数 在 from fibo import * 的时候将不会被引入,类似私有的性质。
    return a+b

print(1,__name__)
if __name__ == "__main__": # 这里执行脚本的时候会执行
    print(2,__name__)
    import sys
    fib(int(sys.argv[1]))

现在执行我们的脚本:

E:\script\python>python fibo.py 5
1 __main__
2 __main__
__main__ 1 2
1 1 2 3

如果我们只是引入模块:

>>> import fibo
1 fibo

上面判断__name__的值是否等于"__main__"的机理就是我们测试我们模块的功能单元。

lua模块申明和使用

lua没有提供类似python的模块机制,或者像其他语言,java和perl的packages,或者c++里面的namespaces等等。但是lua也很容易做到上面类似的事情,就是把table当package用。lua的基础库就是这么干的~。
一个明显的好处就是我们可以使用lua本身table的各项功能,我们可以像操作table一样操作我们的”package”,而且还能把这个”package”赋值给其他变量,或者作为参数传递给函数。听着是不是很棒!(在很多语言中,packages不是作为类的存在)

下面我们写一个操作复数的lua脚本:

--定义包的私有函数

--检查复数
local function checkComplex(c)
    if not ((type(c) == "table") and tonumber(c.r)
        and tonumber(c,i)) then
        -- 报错
        error("bad complex number",3)
    end
end

--构造一个复数
local function new(r,i) return {r=r,i=i} end

--申明一个标准i
--P.i = new(0,1)

--复数加法
local function add(c1,c2)
    checkComplex(c1)
    checkComplex(c2)
    return new(c1.r+c2.r,c1.i+c2.i)
end

--复数减法
local function sub(c1,c2)
    return new(c1.r-c2.r,c1.i-c2.i)
end

--复数乘法
local function mul(c1, c2)
  return new(c1.r*c2.r - c1.i*c2.i,
                     c1.r*c2.i + c1.i*c2.r)
end

-- 自定义需要导出的函数,或者变量
-- lua不能像其他语言那样,把申明放在开始的位置,它需要先申明函数。
-- 这样做的目的是为了包内函数之间相互调用不用加前缀“complex”
complex = {
    new = new,
    add = add,
    sub = sub,
    mul = mul,
    i = new(0,1),
}

-- 这个返回语句不是必须的。因为这个包已经赋
-- 值给全局变量complex中,但是建议你这样做
return complex 

脚本保存在 E:\script\lua中,打开控制台命令,定位到该目录,调起lua:

> require "complex" -- 引入我们的复数包,require对同一个包只引入一次
table: 00000000004c9d40
> complex
table: 00000000004c9d40
> complex.i
table: 00000000004c9d80
> complex.i.r
0
> complex.i.i
1
> complex.new
function: 00000000004caff0
> complex.add
function: 00000000004c9d00
> complex.sub
function: 00000000004cb080
> complex.mul
function: 00000000004cb0b0

> complex.add(1,2)--因为不是复数,所以调用报错。
bad complex number
stack traceback:
        [C]: in function 'error'
        .\complex.lua:8: in upvalue 'checkComplex'
        .\complex.lua:20: in function 'complex.add'
        (...tail calls...)
        [C]: in ?

最好的习惯就是保持我们的lua文件名跟我们的lua包名一致。

require语句的搜索路径是根据package.path去搜索的,我们可以看下package.path:

> package.path
D:\sdk\lua\lua\?.lua;D:\sdk\lua\lua\?\init.lua;D:\sdk\lua\?.lua;D:\sdk\lua\?\init.lua;D:\sdk\lua\..\share\lua\5.3\?.lua;D:\sdk\lua\..\share\lua\5.3\?\init.lua;.\?.lua;.\?\init.lua

里面的问号就是require后面跟的内容。

python模块更多信息

python模块查找原理:当一个模块 A 被引入的时候,python解释器首先先查找内建模块,然后在sys.path给的路径列表搜索A.py文件。
sys.path目录构成有:

  • 脚本引入目录(当前目录)
  • PYTHONPATH指定目录
  • installation-dependent 默认目录

编译过的python文件
为了提高模块的加载速度,python缓存了版本编译过的模块,放在__pycache__目录中,命名:module.version.pyc,这个文件是跨平台的。(文章上面已经展示图片)
python编译也是增量编译的,会检测源文件距离上次编译是否修改。
python在两种情况下不会检测缓存,第一种是没有缓存的文件夹;第二是都使用了已经编译的模块。

如果我们编译python文件的时候,可以加上-O或者-OO参数来优化,生成了module.version.pyo。-O优化掉了assert语句,-OO优化掉了assert语句还有__doc__字符串.他们之间的运行速度并没有变化,只是加载的速度不同,当然优化后编译的文件会缩小。
编译命令:

python -m compileall fibo.py #编译pyc文件,二进制文件,保护代码
python -OO -m compileall fibo.py #编译pyo文件,二进制文件,保护代码

python 提供了很多内建模块方便我们使用。如sys,它有很多函数,常人是很难都记住的,如果不查阅文档,怎么能快速的浏览呢?
python为我们提供了dir函数:

>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loade
r__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdou
t__', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_getframe',
'_home', '_mercurial', '_xoptions', 'api_version', 'argv', 'base_exec_prefix', '
base_prefix', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats',
'copyright', 'displayhook', 'dllhandle', 'dont_write_bytecode', 'exc_info', 'exc
epthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_rep
r_style', 'getallocatedblocks', 'getcheckinterval', 'getdefaultencoding', 'getfi
lesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof'
, 'getswitchinterval', 'gettrace', 'getwindowsversion', 'hash_info', 'hexversion
', 'implementation', 'int_info', 'intern', 'last_traceback', 'last_type', 'last_
value', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', '
path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'setcheckinterval', 's
etprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'std
in', 'stdout', 'thread_info', 'version', 'version_info', 'warnoptions', 'winver'
]

如果dir不传参数,那么默认输出当前定义的变量,模块,函数:

>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__',
 'sys']

但是上面没有包含当前内建的函数和变量。如果我们想要看一下:

>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'Blocki
ngIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError
', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'Conne
ctionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentErro
r', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPoint
Error', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarni
ng', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError',
'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'Non
e', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'O
verflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupErr
or', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'Sto
pIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabEr
ror', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeEr
ror', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWar
ning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionErro
r', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__',
'__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'boo
l', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex'
, 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval
', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals
', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubc
lass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', '
min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit'
, 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'st
aticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

python包
包可以结构化python的模块,通过.访问,类似Java,A.B 意味着有一个B模块在A包里。
包的一般结构是这样的:

sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

目录中的__init__.py是必须的文件,内容可以为空,也可以是包的一些初始化代码,或者__all__变量,后面将介绍。

包的使用跟模块差不多,类似:

import package1.package2.xxx
from package1.package2 import xxx

使用from package import item格式的时候,item可以是子模块(子包),也可以是模块函数,类或者变量,import语句先检查item是否是包的定义,如果没找到,那么检查是否是模块的定义,并尝试加载它,如果没找到,那么报ImportError.
但是如果用import item.subitem.subsubitem的时候,除了最后一个item,其他item都必须是包(package);最后一个item可以是模块或者是包,但不能是之前定义的类、函数、变量。

from package.subpackage import *,这个语句跟模块不同,它不能把包下面的子包都引入,需要我们在__init__.py文件中定义 __all__这个加载列表,比如我们的文件sound/effects/__init__.py 可以这样定义我们需要加载的模块:

#指定我们需要加载的包
__all__ = ["echo", "surround", "reverse"]

包内的相互引用:比如我们的 sound.filters.vocoder 需要 sound.effects 包里的 echo 模块,我们可以这样申明引用:from sound.effects import echo;也可以用相对路径: . 代表当前路径,.. 代表上一层路径;

#我们可以这样使用
from . import echo
from .. import formats
from ..filters import equalizer

最后一个包的功能是 __path__,它可以指定一个list,它可以为我们的包提供更多的搜索路径,但是这个不常用,一般都是我们的包当前路径。

lua table 更多内容

既然我们lua都已经讲到模块了,那么我们有必要补充一下metatable (元表)的知识了。
lua中的table拥有一些列可预见的操作。
比如申明一个普通的table:a = {x=1,y=2},如果我们访问a.x,那么打印1,如果我们访问a.y,那么打印2,如果我们访问a.z,那么打印nil。但是如果我们想要a.z有值呢?当然不是简单的a.z = xx赋值这么简单的方法。这时候就需要我们的元表了。
lua提供了内建函数getmetatablesetmetatable
lua的元表可以是自身,也可以是别的table:

> a = {x=1,y=2}
> setmetatable(a,a) -- 设置a的元表为自己
table: 003ea930 -- 返回table a
> getmetatable(a) -- 返回a的元表
table: 003ea930
> b = {z=3}
> setmetatable(a,b) -- 设置a的元表为b
table: 003ea930
> getmetatable(a)
table: 003eb328
> a.z -- 猜猜看我们能打印出3吗?

好,既然上面不能打印3那就疑问了,我们设置这个干啥呢?这个需要配合元方法使用!上面这个例子后面还会说,我们先来看最简单的元方法-算术元方法。
举个例子,通常我们想要两个table实现+运算是不行的:

> a = {1}
> b = {2}
> a + b
stdin:1: attempt to perform arithmetic on a table value (global 'a')
stack traceback:
        stdin:1: in main chunk
        [C]: in ?

好,下面我们来定义一个集合,保存为set.lua文件:

Set = {}

Set.mt = {}

function Set.new(t)
    local set = {}
    setmetatable(set,Set.mt)
    for _,v in pairs(t) do 
        table.insert(set,v) 
    end
    return set
end

function Set.add(a,b)
    local ret = Set.new{} -- 等价于 Set.new({}),以前提过
    for _,v in pairs(a) do 
        table.insert(ret,v)  
    end
    for _,v in pairs(b) do 
        table.insert(ret,v)  
    end
    return ret
end

Set.mt.__add = Set.add -- 注意这里的 __add

我们引入这个模块,然后执行我们的加法:

> require "set"
true
> s1 = Set.new{1,2}
> s2 = Set.new{4,5}
> s3 = s1 + s2
> for k,v in pairs(s3) do print(k,v) end
1       1
2       2
4       4
5       5

到这里,我们发现我们可以加法了呢。。。为啥呢???
仔细看我们上面的元表Set.mt,我们对它增加了一个方法Set.mt.__add,如果我们尝试把它赋值为nil:

> Set.mt.__add = nil
> s1 + s2 -- 报错了。
stdin:1: attempt to perform arithmetic on a table value (global 's1')
stack traceback:
        stdin:1: in main chunk
        [C]: in ?
> Set.mt.__add = Set.add -- 这里再改回来!

所以呢,我们两个Set能相加,多亏了我们的__add方法。我们称之为元方法,类似的方法还有很多:

__add    重载 +  -- 类似C++中的重载操作符。
__sub    重载 -
__mul    重载 *
__div    重载 /
__unm    重载 not
__pow    重载 ^
__concat 重载 ..

另外,lua对于自定义类型(table)的操作运算遵循:
1. 如果第一个值有元表,并且有对应的操作符重载,lua选择它作为这次运算的方法。不依赖第二个值
2. 否则,如果第二个值有元表,并且有对应的操作符重载,lua选择它作为这次运算的方法。
3. 否则,lua raises an error(报错)

按照上面的规则,1+s1s1+1是等价的,想一想为什么?

如果我们直接去运算 s1+1会报错,我们需要加条件判断:

function Set.add(a,b)
    -- 增加对a和b的判断
    if getmetatable(a) ~= Set.mt or getmetatable(b) ~= Set.mt then
        error("attempt to `add' a set with a non-set value")
    end

    local ret = Set.new{} -- 等价于 Set.new({})
    for _,v in pairs(a) do 
        table.insert(ret,v)  
    end
    for _,v in pairs(b) do 
        table.insert(ret,v)  
    end
    return ret
end

再试一下:

> s1 + 1 -- 现在就是报我们自己的error了
.\set.lua:17: attempt to `add' a set with a non-set value
stack traceback:
        [C]: in function 'error'
        .\set.lua:17: in metamethod '__add'
        stdin:1: in main chunk
        [C]: in ?

还有关系元方法:

__eq 重载 ==  -- 没有~=,可以用 not ==
__lt 重载 <   -- 没有>,可以换个方向 
__le 重载 <=  -- 没有>=,可以换个方向 

改造一下我们的脚本:

function Set.check(a,b)
    -- 增加对a和b的判断
    if getmetatable(a) ~= Set.mt or getmetatable(b) ~= Set.mt then
        error("attempt to operate a set with a non-set value")
    end
end

function Set.le(a,b)
    Set.check(a,b)
    return #a <= #b
end


function Set.lt(a,b)
    return a <= b and not (b <= a)
end

function Set.eq(a,b)
    return a <= b and b <= a
end

Set.mt.__le = Set.le
Set.mt.__lt = Set.lt
Set.mt.__eq = Set.eq

退出重新引入一下:

> Set.new{1,2,3} == Set.new{4,5,6} -- 长度一致
true
> {1,2,3} == {4,5,6} -- 没有重载就会去判断对象是否同一个
false
> {1,2,3} == {1,2,3} -- 这里跟python不同
false

关系元方法跟算术元方法不一样,它不支持混合类型。如果你比较两个不同类型的数据(string和number)的大小,或者拥有不同关系元方法的对象的大小,lua将报错,但是==是另外。==的两边如果是不同类型的数据,那么直接返回false,或者都是table,但是他们的关系元方法不一样,那么也是返回false,仅当都是对象(table),且关系元方法一致,那么lua将会调用这个元方法。

还有一些其他的元方法:

__tostring  重载内建函数 tostring()
__metatable 重载内建函数 getmetatable()

看例子:
对我们的脚本增加下面代码:

function Set.checkset(a)
    if getmetatable(a) ~= Set.mt then
        error("attempt to operate with a non-set value")
    end
end

function Set.tostring(a)
    Set.checkset(a)
    local ret = "{"
    local sep = ""
    for k,v in pairs(a) do 
        ret=ret..sep..v
        sep = ","
    end
    return ret.."}"
end

重新引入:

> s1 = Set.new{1,2,3}
> s1
{1,2,3} -- 这就是我们想要的打印.

-- 如果不想别人修改你的metatable,那么我们可以定义一个__metatable
> Set.mt.__metatable = "not your business"
> getmetatable(s1)
not your business
> setmetatable(s1,s1) 
stdin:1: cannot change a protected metatable
stack traceback:
        [C]: in function 'setmetatable'
        stdin:1: in main chunk
        [C]: in ?

-- 无法修改保护的metatable,OK目的达成

以上基础元方法已经介绍完毕,最后看两个特殊的元方法__index__newindex,它们就可以完成我们一开始的a.z任务^_^
所有的table,它们在访问成员的时候其实通过__index
方法去找的,如果找到返回这个值,如果没找到返回nil。
先来看看我们的最新脚本文件set.lua:

Set = {}

Set.mt = {}
Set.mt.__metatable = "sorry abourt this"

function Set.new(t)
    local set = {}
    setmetatable(set,Set.mt)
    for _,v in pairs(t) do 
        table.insert(set,v) 
    end
    return set
end

function Set.checkset(a)
    if getmetatable(a) ~= Set.mt.__metatable then
        error("attempt to operate with a non-set value")
    end
end

function Set.check(a,b)
    -- 增加对a和b的判断
    if getmetatable(a) ~= Set.mt.__metatable 
        or getmetatable(b) ~= Set.mt.__metatable then
        error("attempt to operate a set with a non-set value")
    end
end

function Set.add(a,b)
    Set.check(a,b)
    local ret = Set.new{} -- 等价于 Set.new({})
    for _,v in pairs(a) do 
        table.insert(ret,v)  
    end
    for _,v in pairs(b) do 
        table.insert(ret,v)  
    end
    return ret
end

function Set.le(a,b)
    Set.check(a,b)
    return #a <= #b
end


function Set.lt(a,b)
    return a <= b and not (b <= a)
end

function Set.eq(a,b)
    return a <= b and b <= a
end

function Set.tostring(a)
    Set.checkset(a)
    local ret = "{"
    local sep = ""
    for k,v in pairs(a) do 
        ret=ret..sep..v
        sep = ","
    end
    return ret.."}"
end


Set.mt.__add = Set.add
Set.mt.__le = Set.le
Set.mt.__lt = Set.lt
Set.mt.__eq = Set.eq
Set.mt.__tostring = Set.tostring

重新引入文件,然后看这个例子:

> s1 = Set.new{1,2,3} -- 申明一个新的Set
> s1 -- 看打印
{1,2,3}
-- 我们申明__index函数,打印参数table和key,并返回nil
> Set.mt.__index = function(tab,key) print(tab,key) return nil end
> s1.a -- 跟原来一样
a
> s1.b -- 区别来了。。。
{1,2,3,a}       b
nil

通过上面的例子我们看到,当调用s1里面含有的key的时候跟原来一样,但是s1.b的时候,由于我们的s1里面并不含有,所以就调用了我们给__index赋值的函数,打印输出,当然结果还是nil.
回到最开始的例子(a.z):

> a = {x=1,y=2}
> b = {z=3}
> setmetatable(a,b)
table: 003eee58
> b.__index = function(tab,key) return b[key] end
> a.z
3 -- OK 我们想要的结果来了~

其实我们的__index可以是function也可以是table,如果是function,那么会把调用的table和key作为参数传过去;如果是table,那么lua只是重新查询,如果查到返回,没有则继续调用这个table的__index元方法。
利用这一点我们可以完成类的继承这样的功能。具体后面再细说。
如果我们只是想访问这个table的元素,避开它的__index调用,我们可以调用内建函数rawget(table,key):

> rawget(a,"x") -- 避开__index
1
> rawget(a,"z") -- 避开__index
nil

rawget函数并不能给我们的程序提速,但是我们有时候需要用它。

__newindex元方法是更新table,当我们给table赋值一个本不存在的key的时候,lua解释器先去找这个table的__newindex,如果不为nil,则调用它;如果为nil,就对table做赋值操作:

> a = {}
> setmetatable(a,a)
table: 0033aa88
> a.__newindex = function(tab,k,v) return print(tab,k,v) end
> a.x = 1
table: 0033aa88 x       1
>a.x
nil

__newindex的值可以是table,这样赋值操作将会在那个table上发生,原table不会做任何事。

> a = {}
> b = {}
> setmetatable(a,b)
table: 00c6f0a0
> b.__newindex = b -- 想想这里能不能赋值 a ?
> a.x = 1
> a.x
nil
> b.x
1
> rawset(a,'y',2) -- 这个跟rawget有点类似,避开__newindex
table: 0068a9c0
> a.y
2
> b.y
nil
> b.__index = b
> a.x
1

table的默认值是nil,我们可以用__index修改这个默认值,想想可以怎么做?

利用__index__newindex我们可以监视某个空table的操作:

-- create private index
local index = {}

-- create metatable
local mt = {
  __index = function (t,k)
    print("*access to element " .. tostring(k))
    return t[index][k]   -- access the original table
  end,

  __newindex = function (t,k,v)
    print("*update of element " .. tostring(k) ..
                         " to " .. tostring(v))
    t[index][k] = v   -- update original table
  end
}

function track (t)--监视函数,要监视某个table,只需t=track(t)
  local proxy = {}
  proxy[index] = t
  setmetatable(proxy, mt)
  return proxy
end

把table变为只读:

function readOnly (t)
  local proxy = {}
  local mt = {       -- create metatable
    __index = t,
    __newindex = function (t,k,v)
      error("attempt to update a read-only table", 2)
    end
  }
  setmetatable(proxy, mt)
  return proxy
end

使用方法:

days = readOnly{"Sunday", "Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday"}

print(days[1])     --> Sunday
days[2] = "Noday"
stdin:1: attempt to update a read-only table

未完待续…

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值