定义函数参数的默认值

rip*_*pat 82 lua function

在Lua wiki中,我找到了一种为缺少参数定义默认值的方法:

function myfunction(a,b,c)
    b = b or 7
    c = c or 5
    print (a,b,c)
end
Run Code Online (Sandbox Code Playgroud)

这是唯一的方法吗?PHP风格myfunction (a,b=7,c=5)似乎不起作用.并不是Lua方式不起作用,我只是想知道这是否是唯一的方法.

Stu*_*ley 81

如果你想要命名参数和默认值,如PHP或Python,你可以使用表构造函数调用你的函数:

myfunction{a,b=3,c=2}
Run Code Online (Sandbox Code Playgroud)

(这在Lua的许多地方都可以看到,比如IUPLua中的高级形式的LuaSocket协议模块和构造函数.)

函数本身可以有这样的签名:

function myfunction(t)
    setmetatable(t,{__index={b=7, c=5}})
    local a, b, c =
      t[1] or t.a, 
      t[2] or t.b,
      t[3] or t.c
    -- function continues down here...
end
Run Code Online (Sandbox Code Playgroud)

参数表中缺少的任何值都将从__index其metatable中的表中获取(请参阅metatables文档).

当然,使用表构造函数和函数可以实现更高级的参数样式 - 您可以根据需要编写任何内容.例如,是一个函数,它构造一个函数,该函数从定义参数名称和默认值的表以及采用常规参数列表的函数中获取命名或位置参数表.

作为非语言级别的功能,可以更改此类调用以提供新的行为和语义:

  • 可以使变量接受多个名称
  • 位置变量和关键字变量可以散布 - 并且定义两者都可以优先于(或导致错误)
  • 可以创建仅关键字无位置变量,以及无名称位置变量
  • 可以通过解析字符串来完成相当冗长的表构造
  • 如果使用1表以外的函数调用函数,则可以逐字使用参数列表

用于编写参数转换器的一些有用函数是unpack(转到table.unpack5.2中),setfenv(在5.2中使用新_ENV构造弃用),以及select(从给定参数列表返回单个值,或者列表的长度'#').

  • 应该注意的是,在这个回复中也使用的 `x or default` 表达式并不是真正等效于参数默认值,而是一种简单的解决方法,只有在 `nil` 和 `false` 都是无效参数值时才有效。假设布尔参数 `x` 的默认值为 `true`,调用者传递一个显式的 `false`。然后 `x or true` 给出 `true`,即使 `false` 被显式传递。更好的版本是`if x == nil then x = default end`,这也更具可读性;尽管如此,它仍然无法处理显式的 `nil` 参数。 (2认同)

jpj*_*obs 44

在我看来,没有其他办法.这只是Lua的心态:没有多余的装饰,除了一些语法糖,没有多余的方式做简单的事情.

  • Stuart P. Bentley在下面的回答完全证明了这个答案,其中函数是用表构造函数调用的.这是Lua的优点之一:虽然它没有一堆装饰,但是Lua的基本构建块允许你做无限的事情,对于语言的小尺寸和简单性来说真的非常了不起. (9认同)

Stu*_*ley 20

从技术上讲,有b = b == nil and 7 or b(在应用为7 false的有效值的情况下应该使用false or 7),但这可能不是你想要的.

  • 由于OP在他们的问题中提到,我认为这是多余的(问题是关于如何定义默认变量*除了*`b = b或7`). (2认同)

LMD*_*LMD 6

一如既往,“Lua 给你力量,你构建机制”。这里要做的第一个区别是命名参数和常用参数列表之间的区别。

参数列表

假设您的所有参数都在参数列表中给出,如下所示,它们都将被初始化。此时,您无法区分“未通过”和“已通过nil” - 两者都只是nil。设置默认值的选项有:

  1. or如果您期望一个真值(notnilfalse),请使用运算符。在这种情况下,即使false给出了默认值也可能是一个功能。
  2. 使用显式nil检查param == nil,用作if param == nil then param = default end或典型的 Lua 三元构造param == nil and default or param

如果您发现自己经常重复第 (2) 点中的模式,您可能需要声明一个函数:

function default(value, default_value)
    if value == nil then return default_value end
    return value
end
Run Code Online (Sandbox Code Playgroud)

(此函数是否使用全局作用域或局部作用域是另一个问题,我不会在这里讨论)。

我在以下示例中包含了所有三种方式:

function f(x, y, z, w)
    x = x or 1
    y = y == nil and 2 or y
    if z == nil then z = 3 end
    w = default(w, 4
    print(x, y, z, w)
end
f()
f(1)
f(1, 2)
f(1, 2, 3)
f(1, 2, 3, 4)
Run Code Online (Sandbox Code Playgroud)

请注意,这也允许省略中间的参数;尾随nil参数也将被视为不存在:

f(nil)
f(nil, 2, 3)
f(nil, 2, nil, 4)
f(1, 2, 3, nil)
Run Code Online (Sandbox Code Playgroud)

可变参数

nilLua 的一个鲜为人知的功能是能够实际确定传递了多少个参数,包括通过函数区分显式传递的参数和“无参数”的能力select。让我们用这个重写我们的函数:

function f(...)
    local n_args = select("#", ...) -- number of arguments passed
    local x, y, z, w = ...
    if n_args < 4 then w = 4 end
    if n_args < 3 then z = 3 end
    if n_args < 2 then y = 2 end
    if n_args < 1 then x = 1 end
    print(x, y, z, w)
end
f() -- prints "1 2 3 4"
f(nil) -- prints "nil 2 3 4"
f(1, nil) -- prints "1 nil 3 4"
f(1, nil, 3) -- prints "1 nil 3 4"
f(nil, nil, nil, nil) -- prints 4x nil
Run Code Online (Sandbox Code Playgroud)

警告:(1)参数列表被拖入函数中,损害了可读性(2)手动编写相当麻烦,并且可能应该被抽象掉,也许需要时间使用提供适当默认值的包装函数。 wrap_defaults({1, 2, 3, 4}, f)这个实现留给读者作为练习(提示:直接的方法是首先将参数收集到垃圾表中,然后在设置默认值后将其解压)。

表调用

Lua 提供了用单个表作为唯一参数调用函数的语法糖:f{...}相当于f({...}). 此外,{f(...)}可用于捕获返回的可变参数f(警告:如果f返回nils,表的列表部分将有漏洞)。

表还允许将命名“参数”实现为表字段:表允许混合列表和散列部分,从而使f{1, named_arg = 2}Lua 完全有效。

就限制而言,表调用的优点是它只在堆栈上留下单个参数(表),而不是多个参数。对于递归函数,这允许稍后遇到堆栈溢出。由于 PUC Lua 将堆栈限制大幅增加到约 1M,这不再是一个问题;然而,LuaJIT 的堆栈限制仍然约为 65k,而 PUC Lua 5.1 甚至更低,约为 15k。

从性能和内存消耗来看,表调用显然更差:它需要Lua建立一个垃圾表,然后垃圾表会浪费内存,直到GC将其清除。因此,垃圾参数表可能不应该在发生大量调用的热点中使用。对哈希图进行索引显然也比直接从堆栈中获取值要慢。

也就是说,让我们检查一下实现表默认值的方法:

拆包/解构

unpacktable.unpack在更高版本(5.2+)中)可用于将表转换为可变参数,可变参数可以被视为参数列表;但请注意,在 Lua 中,列表部分不能有尾随nil值,不允许您区分“无值”和nil。对局部变量进行解包/解构也有助于提高性能,因为它消除了重复的表索引。

function f(params)
    local x, y, z, w = unpack(params)
    -- use same code as if x, y, z, w were regular params
end
f{1, 2, nil}
Run Code Online (Sandbox Code Playgroud)

如果您使用命名字段,则必须显式解构这些字段:

function f(params)
    local x, y, z, w = params.x, params.y, params.z, params.w
    -- use same code as if x, y, z, w were regular params
end
f{x = 1, w = 4}
Run Code Online (Sandbox Code Playgroud)

可以混合搭配:

function f(params)
    local x, y, z = unpack(params)
    local w = params.w
    -- use same code as if x, y, z, w were regular params
end
f{1, 2, w = 4}
Run Code Online (Sandbox Code Playgroud)

元表

元表字段可用于设置使用if is__index索引的表,提供默认值。在传递的表上设置元表的一个主要缺点是传递的表的元表将丢失,这可能会导致调用方出现意外的行为。在完成操作后,您可以使用和来恢复元表,但这会很脏,因此我建议不要使用它。nameparams.namenilnilgetmetatablesetmetatableparams

坏的

function f(params)
    setmetatable(params, {__index = {x = 1, y = 2, z = 3, w = 4}})
    -- use params.[xyzw], possibly unpacking / destructuring
end
f{x = 1}
Run Code Online (Sandbox Code Playgroud)

除了可能的垃圾参数表之外,每次调用该函数时,还会创建 (1) 垃圾元表和 (2) 垃圾默认表。这很糟糕。由于元表是常量,只需将其拖出函数,使其成为上值:

好的

local defaults_metatable = {__index = {x = 1, y = 2, z = 3, w = 4}}
function f(params)
    setmetatable(params, defaults_metatable)
    -- use params.[xyzw], possibly unpacking / destructuring
end
Run Code Online (Sandbox Code Playgroud)

避免元表

如果您想要一个没有元表的 hackyness 的默认表,请考虑再次编写一个辅助函数来完成具有默认值的表:

local function complete(params, defaults)
    for param, default in pairs(defaults) do
        if params[param] == nil then
            params[param] = default
        end
    end
end
Run Code Online (Sandbox Code Playgroud)

这将更改params表格,正确设置默认值;用于params = complete(params, defaults)。再次,记住将defaults表格拖出函数。


Nin*_*yes 5

到目前为止我发现的唯一有意义的方法是做这样的事情:

function new(params)
  params = params or {}
  options = {
    name = "Object name"
  }

  for k,v in pairs(params) do options[k] = v end

  some_var = options.name
end

new({ name = "test" })
new()
Run Code Online (Sandbox Code Playgroud)