从python到lua的学习笔记

2023-06-22  本文已影响0人  小餐包

Lua tutorial for python developer

基础

命令行

# 进入交互模式
lua -i 

# 类似于python -c "print(math.sin(12))"
lua -e "print(math.sin(12))"

# arg[0]表示脚本

注释

变量

运算符

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

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"]

数据类型

循环

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

解析:

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参数也是在调用时隐含的作为函数的第一个参数传递。

可变参数

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))
function f(...)
    a = select(3,...)  -->从第三个位置开始,变量 a 对应右边变量列表的第一个参数
    print (a)
    print (select(3,...)) -->打印所有列表参数
end

>f(0,1,2,3,4,5)
2
2       3       4       5

常用方法

字符串方法

表方法

fruits = {"banana","orange","apple"}
-- 返回 table 连接后的字符串
print("连接后的字符串 ",table.concat(fruits))

-- 指定连接字符
print("连接后的字符串 ",table.concat(fruits,", "))

-- 指定索引来连接 table
print("连接后的字符串 ",table.concat(fruits,", ", 2,3))
fruits = {"banana","orange","apple"}

-- 在末尾插入
table.insert(fruits,"mango")
print("索引为 4 的元素为 ",fruits[4])

-- 在索引为 2 的键处插入
table.insert(fruits,2,"grapes")
print("索引为 2 的元素为 ",fruits[2])

模块与包

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__等)的特殊方法(也叫魔法方法):

常见元方法

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库提供了两个通用的错误处理函数:

>=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方法有:

完全模式

使用 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,其中包含:

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

debug.getlocal

代码规范

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

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

上一篇下一篇

猜你喜欢

热点阅读