美文网首页
从python到lua的学习笔记

从python到lua的学习笔记

作者: 小餐包 | 来源:发表于2023-06-22 20:30 被阅读0次

    Lua tutorial for python developer

    基础

    命令行

    # 进入交互模式
    lua -i 
    
    # 类似于python -c "print(math.sin(12))"
    lua -e "print(math.sin(12))"
    
    # arg[0]表示脚本
    
    • 交互模式中使用=变量名可以打印变量的值;
    • 解释器在运行脚本前会为所有的命令行参数创建一个名为“arg”的table,脚本名称位于索引0上,它的第1个参数位于索引1,以此类推,脚本之前的参数位于负数索引上;

    注释

    • 单行:

      --
      
    • 多行:

      --[[
      注释部分
      --]]
      

    变量

    • Lua 中的变量默认是全局变量,哪怕是语句块或是函数里,除非用 local 显式声明为局部变量;

    • 跟python一样,lua是动态语言,变量的类型不需要预定义而是在runtime的时候决定;

    • 应该尽可能的使用局部变量以避免命名冲突,同时局部变量的访问速度比全局变量快;

    • Lua将其所有的全局变量保存在一个常规的table中,这个table成为环境(environment),这个table自身保存在一个全部变量_G中。

      比如:

      if not rawget(_G, "ConfigMgr") then
          rawset(_G, "ConfigMgr", {})
      end
      

    运算符

    逻辑运算符包括 andornot。与控制结构类似,所有逻辑运算符都将 false 和 nil 视为假,其余任何值都视为真,这意味着数字0也被视为真(这个在其他语言里面比较少见)

    • 运算符 and 如果第一个参数为假,则返回该参数;否则返回第二个参数。

    • 运算符 or 如果第一个参数不为假,则返回该参数;否则返回第二个参数。

    • andor 运算符都只在必要时计算第二个操作数,如下:

    print(4 and 5)         --> 5
    print(nil and 13)      --> nil
    print(false and 13)    --> false
    print(4 or 5)          --> 4
    print(false or 5)      --> 5
    

    在python中我们比较习惯使用or给变量赋缺省值,但是lua中存在使用and来赋缺省值的情况:

    local value = my_table and my_table["key"]
    

    数据类型

    • nil:相当于None:

      > type(X)
      nil
      > type(X)==nil
      false
      > type(X)=="nil"
      true
      >
      
    • boolean:

      • true和false, 只有false和nil是false,数字0也是true!
    • number:

      • Lua 默认只有一种 number 类型-double(双精度)类型
    • string:

      • 除了用单引号和双引号以外,还可以用[[]]表示一段文本(文本内容不会进行转义),类似于python中的三个单引号的doc表示方法;

      • 字符串连接使用..而不是+:

        >> print("a"+"b")
        stdin:1: unfinished string near '")'
        > print("a".."b")
        ab
        
      • 使用#计算字符串的长度:

        > s="abcdefg"
        > print(#s)
        7
        
    • function:函数也是一等公民,这意味着函数可以存储到变量中,也可以作为实参传递给其他函数或者作为函数的返回值,总之,lua也支持函数式编程。

    • userdata:表示任意存储在变量中的C数据结构;

    • thread:执行的独立线程,用于执行协同程序;

    • table:

      • 其实是一个关联数组(associate arrays),数组的索引可以是数字、字符串或者table类型;

      • 不同于其他语言的数组把 0 作为数组的初始索引,在 Lua 里表的默认初始索引一般以 1 开始

      • 索引为字符串或者table时,就变成了我们熟悉的字典,比如:

        dd ={A=1,B=2}
        for k,v in pairs(dd) do 
            print(k,v)
        end
        

        注意:作为key时字符串不需要加引号

      • 遍历使用for k, v in pairs(tb) do语句:

        local tbl = {"apple", "pear", "orange", "grape"}
        for key, val in pairs(tbl) do
            print("Key", key)
        end
        
      • 当索引为字符串时,可以使用t.i这种简化写法,意义同t[i]

    循环

    while

    a=10
    while( a < 20 )
    do
       print("a 的值为:", a)
       a = a+1
    end
    

    for

    重点掌握遍历list的方法:

    -- 数值for循环
    for i, v in iparis(array) do
        xxx
    end
    
    -- var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为1。
    for var=exp1,exp2,exp3 do  
        <执行体>  
    end  
    
    for i=10,1,-1 do
        print(i)
    end
    
    -- 泛型for循环
    --打印数组a的所有值  
    a = {"one", "two", "three"}
    -- ipairs相当于python中的enumerate
    for i, v in ipairs(a) do
        print(i, v)
    end
    

    repeat...until

    repeat...until 循环的条件语句在当前循环结束后判断

    repeat
       statements
    until( condition )
    
    --[ 变量定义 --]
    a = 10
    --[ 执行循环 --]
    repeat
       print("a的值为:", a)
       a = a + 1
    until( a > 15 )
    

    条件语句

    if...else语句

    if(布尔表达式)
    then
       --[ 布尔表达式为 true 时执行该语句块 --]
    else
       --[ 布尔表达式为 false 时执行该语句块 --]
    end
    

    if...elseif...else语句

    if( 布尔表达式 1)
    then
       --[ 在布尔表达式 1 为 true 时执行该语句块 --]
    
    elseif( 布尔表达式 2)
    then
       --[ 在布尔表达式 2 为 true 时执行该语句块 --]
    
    elseif( 布尔表达式 3)
    then
       --[ 在布尔表达式 3 为 true 时执行该语句块 --]
    else 
       --[ 如果以上布尔表达式都不为 true 则执行该语句块 --]
    end
    

    迭代器

    closure闭包

    一个closure就是一种可以访问其外部嵌套环境中的局部变量的函数。通过这些变量就可以记住它在一次遍历中所在的位置。

    无状态迭代器

    一种自身不保存任何状态的迭代器,比如ipairs:

    a = {"one", "two", "three"}
    for i, v in ipairs(a) do
        print(i, v)
    end
    

    泛型迭代器

    for <var-list> in <exp-list> do
        <body>
    end
    
    for line in io.lines() do
        io.write(line, "\n")
    end
    

    函数

    函数定义

    Lua 编程语言函数定义格式如下:

    optional_function_scope function function_name(argument1, argument2, argument3..., argumentn)
        function_body
        return result_params_comma_separated
    end
    

    解析:

    • optional_function_scope: 该参数是可选的指定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local
    • function_name: 指定函数名称。
    • argument1, argument2, argument3..., argumentn: 函数参数,多个参数以逗号隔开,函数也可以不带参数。
    • function_body: 函数体,函数中需要执行的代码语句块。
    • result_params_comma_separated: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开。
    myprint = function(param)
       print("这是打印函数 -   ##",param,"##")
    end
    
    function add(num1,num2,functionPrint)
       result = num1 + num2
       -- 调用传递的函数参数
       functionPrint(result)
    end
    myprint(10)
    -- myprint 函数作为参数传递
    add(2,5,myprint)
    

    匿名函数

    lua同样支持匿名函数,比如:

    table.sort(network, function (a,b) return (a.name>b.name) end)
    

    函数调用

    lua为面向对象的调用也提供了一种特殊的语法,表达式o.foo(o, x)的另一种写法是o:foo(x),冒号操作符将使调用o.foo时将o隐含地作为函数的第一个参数,类似于python中对象方法的self参数也是在调用时隐含的作为函数的第一个参数传递。

    可变参数

    • 使用三点 ... 表示函数有可变的参数,通过select("#", ...)来获取可变参数的数量:
    function average(...)
       result = 0
       local arg={...}
       for i,v in ipairs(arg) do
          result = result + v
       end
       print("总共传入 " .. select("#",...) .. " 个数")
       return result/select("#",...)
    end
    
    print("平均值为",average(10,5,3,4,5,6))
    
    • select(n, …) 用于返回从起点 n 开始到结束位置的所有参数列表。
    function f(...)
        a = select(3,...)  -->从第三个位置开始,变量 a 对应右边变量列表的第一个参数
        print (a)
        print (select(3,...)) -->打印所有列表参数
    end
    
    >f(0,1,2,3,4,5)
    2
    2       3       4       5
    
    • 如果不习惯select用法,可以直接把可变参数赋值给table,之后操作table即可,例如上面的例子可以写成():

      function f(...)
          local arg = {...}
          print (arg[3])
          for i = 3, #arg do
            print (arg[i]) -->打印所有列表参数
          end
      end
      
      >f(0,1,2,3,4,5)
      2
      2
      3
      4
      5
      
    • 函数调用时,若实参多余形参,则舍弃多余的实参;若实参不足,则多余的形参初始化为nil;

    • unpack函数接受一个数组作为参数,并从下标1开始返回数组的所有元素,类似于python中的*items语法:

      print(unpack{1,2,3})  --> 1 2 3
      

    常用方法

    字符串方法

    • string.upper()、string.lower()、string.len()

    • string.gsub(mainString, findString, replaceString, num)

      > string.gsub("aaaa","a","z",3);
      zzza    3
      
    • string.find(str, substr, [init, [plain]])

      > string.find("Hello Lua user", "Lua", 1) 
      7    9
      
    • string.format()

      > string.format("the value is:%d",4)
      the value is:4
      
    • string.char(arg) 和 string.byte(arg[,int])

      > string.char(97,98,99,100)
      abcd
      > string.byte("ABCD",4)
      68
      > string.byte("ABCD")
      65
      >
      
    • string.match(str, pattern, init)

      > = string.match("I have 2 questions for you.", "%d+ %a+")
      2 questions
      
      > = string.format("%d, %q", string.match("I have 2 questions for you.", "(%d+) (%a+)"))
      2, "questions"
      
    • string.sub(s, i [, j])

      • s:要截取的字符串。
      • i:截取开始位置。
      • j:截取结束位置,默认为 -1,最后一个字符。
      id="#autoqa1"
      
      =id:sub(2)
      autoqa1
      
      =id:sub(#id)
      1
      

    表方法

    • table.concat(table [, sep [, start [, end]]])
    fruits = {"banana","orange","apple"}
    -- 返回 table 连接后的字符串
    print("连接后的字符串 ",table.concat(fruits))
    
    -- 指定连接字符
    print("连接后的字符串 ",table.concat(fruits,", "))
    
    -- 指定索引来连接 table
    print("连接后的字符串 ",table.concat(fruits,", ", 2,3))
    
    • table.insert(table, [pos,] value) 在table的数组部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾.
    fruits = {"banana","orange","apple"}
    
    -- 在末尾插入
    table.insert(fruits,"mango")
    print("索引为 4 的元素为 ",fruits[4])
    
    -- 在索引为 2 的键处插入
    table.insert(fruits,2,"grapes")
    print("索引为 2 的元素为 ",fruits[2])
    
    • table.remove (table [, pos])返回table数组部分位于pos位置的元素. 其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起。

      -- 创建一个包含 5 个元素的数组表
      local myTable = {10, 20, 30, 40, 50}
      
      -- 移除第三个元素(值为 30)
      table.remove(myTable, 3)
      
      -- 移除元素后的数组 {10,20,40,50}
      

    模块与包

    Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。

    -- util.lua
    util = {}
    util.PATH = "/home/admin"
    function util.foo()
        print("foo")
    end
    
    local function bar()
        print("bar")
    end
    
    return util
    

    上述的bar声明为一个局部变量,即表示一个私有函数,因此是不能从外部访问模块里的私有函数的,必须通过公有函数来调用。

    require 函数

    Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:

    require("<模块名>")
    

    或者

    require "<模块名>"
    

    比如:

    -- main.lua
    require 'util'
    
    util.foo()
    print(util.PATH)
    

    如果希望使用较短的模块名称,则可以设置一个局部名称,比如:

    local m = require "mod"
    m.foo()
    

    即使知道某些用到的模块可能已经加载了,但只要用到require就是一个良好的编程习惯。可以将标准库排除在此规则之外,因为Lua总数会预先加载它们。只要一个模块已经加载过,后续的require调用都将返回同一个值。如果require为指定模块找到了一个Lua文件,它就通过loadfile来加载该文件。而如果找到的是一个C程序库,则通过loadlib来加载

    加载机制

    类似于PYTHON_PATH,lua使用LUA_PATH这个环境变量来搜索模块。

    如果LUA_PATH中无法找到与模块名相等的Lua文件,它就会找C程序库,搜索路径由LUA_CPATH来定义。

    在搜索一个文件时,require所使用的路径是一连串的模式(pattern),其中每项都是一种将模块名转换为文件名的方式。进一步说,require会用模块名来替换每个?,然后根据替换的结果来检查是否存在这样一个文件。如果不存在,就会尝试下一项。

    Windows上,安装时自动配置的LUA_PATH的值是这个;;C:\Program Files (x86)\Lua\5.1\lua\?.luac

    元表

    类似于python中__XXX__(比如__repr__等)的特殊方法(也叫魔法方法):

    • setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
    • getmetatable(table): 返回对象的元表(metatable)。

    常见元方法

    lua元方法 python方法 说明
    __index __getattr__
    __newindex __setattr__
    __call __call__
    __tostring_ __str_

    错误处理

    assert首先检查第一个参数,若没问题,assert不做任何事情;否则,assert以第二个参数作为错误信息抛出。

    local function add(a,b)
       assert(type(a) == "number", "a 不是一个数字")
       assert(type(b) == "number", "b 不是一个数字")
       return a+b
    end
    add(10)
    

    pcall接收一个函数和要传递给后者的参数,并执行,分别返回执行结果,执行是否报错以及错误信息,例如:

    -- 无报错情况
    > =pcall(function(i) print(i) end, 33)
    33
    true
    
    -- 有报错情况
    > =pcall(function(i) print(i) error('error..') end, 33)
    33
    false        stdin:1: error..
    

    Lua提供了xpcall函数,xpcall接收第二个参数——一个错误处理函数,当错误发生时,Lua会在调用桟展开(unwind)前调用错误处理函数,于是就可以在这个函数中使用debug库来获取关于错误的额外信息了。

    debug库提供了两个通用的错误处理函数:

    • debug.debug:提供一个Lua提示符,让用户来检查错误的原因
    • debug.traceback:根据调用桟来构建一个扩展的错误消息
    >=xpcall(function(i) print(i) error('error..') end, function() print(debug.traceback()) end, 33)
    33
    stack traceback:
    stdin:1: in function <stdin:1>
    [C]: in function 'error'
    stdin:1: in function <stdin:1>
    [C]: in function 'xpcall'
    stdin:1: in main chunk
    [C]: in ?
    false        nil
    

    面向对象

    Lua中使用冒号 : 来访问类的成员函数,这种调用方法隐含的传递了一个self参数。

    Lua中最基本的结构是table,所以需要用table来描述对象的属性,可以通过创建一个新的表并将其元表设置为父表来实现对象的继承。假设有两个对象a和b,要让b作为a的一个原型,只需要输入如下语句:

    setmetatable(a, {__index=b})
    

    在此之后,a就会在b中查找所有它没有的操作,更具体的例子如下:

    -- 定义父表
    local parent = {
      name = "Parent",
      greeting = function(self)
        print("Hello from " .. self.name)
      end
    }
    
    -- 定义子表
    local child = {
      name = "Child"
    }
    
    -- 将子表的元表设置为父表
    setmetatable(child, { __index = parent })
    
    -- 子表现在可以访问父表中的属性和方法
    child:greeting() 
    

    setmetatable函数是Lua中的一个内置函数,用于设置一个表的元表。元表是一个包含特殊方法的表,当对一个表进行某些操作时,如果这个表没有定义对应的方法,Lua就会在它的元表中查找这个方法。因此,通过设置一个表的元表,我们可以为它提供默认的行为或者覆盖它的一些行为。其中,__index是一个特殊的元方法,当Lua在子表中查找一个不存在的属性或方法时,它会在子表的元表中查找__index所指定的表,并在这个表中查找对应的属性或方法。在上述代码中,我们将子表的元表设置为父表,这意味着当子表在自己的表中找不到对应的属性或方法时,它会在父表中查找,从而实现继承的效果。

    再看一个例子:

    Account = {balance=0}
    function Account:new(obj)
        obj = obj or {}
        setmetable(o, self)
        self.__index = self
        return obj
    end
    
    function Account:deposit(v)
        self.balance = self.balance + v
    
    SpecialAccount = Account:new()
    s=SpecialAccount:new({balance=100})
    

    协程

    当create一个coroutine的时候就是在新线程中注册了一个事件。

    当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件。

    方法:

    coroutine.create(f): 创建 coroutine,参数是一个函数,返回 一个coroutine对象;

    coroutine.resume(co): 唤醒coroutine,参数是coroutine对象;

    coroutine.yield():挂起当前的coroutine,将 coroutine 设置为挂起状态;

    coroutine.status():查看当前coroutine的状态;

    coroutine.wrap():创建coroutine,返回一个函数,一旦你调用这个函数,就进入coroutine;本质就是如果coroutine对象不存在则创建,如果已经创建,则调用resume方法;

    coroutine.running():返回当前正在运行的coroutine;

    function async_sleep(seconds)
        local start = os.time()
        while os.time() - start < seconds do
            coroutine.yield()
        end
    end
    
    local co1 = coroutine.create(function ()
        print("Coroutine 1 started!")
        async_sleep(2)
        print("Coroutine 1 finished!")
    end)
    
    local co2 = coroutine.create(function ()
        print("Coroutine 2 started!")
        async_sleep(1)
        print("Coroutine 2 finished!")
    end)
    
    while coroutine.status(co1) ~= "dead" or coroutine.status(co2) ~="dead" do
        coroutine.resume(co1)
        coroutine.resume(co2)
    end
    

    文件IO

    简单模式:

    -- 简单模式
    -- 以只读方式打开文件
    file = io.open("test.lua", "r")
    
    -- 设置默认输入文件为 test.lua
    io.input(file)
    
    -- 调用io.read()方法读取默认输入文件的第一行
    print(io.read())
    
    -- 关闭打开的文件
    io.close(file)
    
    -- 以附加的方式打开只写文件
    file = io.open("test.lua", "a")
    
    -- 设置默认输出文件为 test.lua
    io.output(file)
    
    -- 在文件最后一行添加 Lua 注释
    io.write("--  test.lua 文件末尾注释")
    
    -- 关闭打开的文件
    io.close(file)
    

    io.read()可以携带如下参数:

    • “*n”: 读取一个数字并返回它。
    • “*a”: 从当前位置读取真个文件。
    • “*I”(默认): 读取下一行,在文件尾处(EOF)返回nil。
    • number: 返回一个指定字符个数的字符串,或再EOF处返回nil

    其他的io方法有:

    • io.tmpfile():返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除
    • io.type(file):检测obj是否一个可用的文件句柄
    • io.flush():向文件写入缓冲中的所有数据
    • io.lines(optional file name):返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回 nil,但不关闭文件。

    完全模式

    使用 file:function_name 来代替 io.function_name 方法

    file = io.open("test.lua", "r")
    print(file:read())
    file:close()
    

    日期和时间

    -- unix时间戳
    > print(os.time())
    1676530122
    
    -- os.date会格式化的输出时间的字符串形式
    > print(os.date()) 
    02/16/23 14:50:42
    
    > print(os.date("%Y/%m/%d %H:%M")) 
    2023/02/16 14:53
    
    -- os.clock()会返回当前CPU时间的秒数,带小数,可以精确到毫秒,通常用来计算一段代码的执行时间。
    > print(os.clock())
    650.732
    
    -- 同步sleep
    require("socket")
    function sleep(seconds)
        socket.sleep(seconds)
    end
    
    -- 异步sleep
    function async_sleep(seconds)
        local start = os.time()
        while os.time() - start < seconds do
            coroutine.yield()
        end
    end
    

    其他系统调用

    -- 获取环境变量
    > print(os.getenv("LUA_PATH")) 
    ;;C:\Program Files (x86)\Lua\5.1\lua\?.luac
    
    -- 相当于python中的system调用
    os.execute("mkdir mydir")
    
    
    -- dofile (FILENAME) 加载文件中的代码,且加载代码时遇到错误会报错。
    -- 加入a.lua文件中定义了如下两个函数
    function f_a(x,y)
      return x + y
    end
    
    function g_a(x,y)
      return x * y
    end
    
    -- 那么可以 dofile 加载这个文件并使用这两个函
    dofile('a.lua')
    f_a(3,4)
    g_a(3,4)
    
    -- unpack函数
    t={1,2,3}
    x,y,z=unpack(t)
    -- x=1,y=2,z=3
    a,b=unpack(t,1,2)
    -- a=1,b=2
    

    调试接口

    调试库的性能不高,并且会打破语言的一些固有规则,因此用户通常不会在最终版本中打开这个库,或者使用debug=nil来删除它。

    stack level概念:调用调试函数时level 1,调用这个函数的level是2,依次类推。

    debug.getinfo

    第一个参数可以是函数或者stack level。比如为某个foo函数调用debug.getinfo(foo) 时就会得到一个table,其中包含:

    • source:函数定义的位置。如果是loadstring定义的,那么会返回完整的字符串,如果是文件中定义,那么就是这个文件名前@
    • short_src:source的短版本(最多60个字符),用于错误信息
    • linedefined
    • lastlinedefined
    • what:函数的类型,分为下面三种
      • Lua
      • C
      • main
    • name: 函数的名称
    • namewhat:可能是
      • global
      • local
      • method
      • field
      • 空字符串:没有找到该函数
    • nups:函数的upvalue数量

    第二个参数是可选的,其值为字符串,用来过滤打印必要的信息,具体如下:

    • n:name和namewhat
    • f:func
    • S:source、short_src、what、linedefined和lastlinedefined
    • l:currentline
    • L:activielines
    • u:nups

    debug.getlocal

    代码规范

    参考这个:http://lua-users.org/wiki/LuaStyleGuide

    关于命名,也是case_snake的风格,基本参照python的命名规范就行了。

    相关文章

      网友评论

          本文标题:从python到lua的学习笔记

          本文链接:https://www.haomeiwen.com/subject/yldmydtx.html