定义函数参数的默认值

发布于 2024-11-07 14:14:13 字数 246 浏览 0 评论 0 原文

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

function myfunction(a,b,c)
    b = b or 7
    c = c or 5
    print (a,b,c)
end

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

In the Lua wiki I found a way to define default values for missing arguments:

function myfunction(a,b,c)
    b = b or 7
    c = c or 5
    print (a,b,c)
end

Is that the only way? The PHP style myfunction (a,b=7,c=5) does not seem to work. Not that the Lua way doesn't work, I am just wondering if this is the only way to do it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

め七分饶幸 2024-11-14 14:14:13

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

myfunction{a,b=3,c=2}

这在 Lua 中的很多地方都可以看到,例如 LuaSocket 协议模块的高级形式IUPLua。)

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

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

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

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

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

  • 可以使变量接受多个名称
  • 位置变量和关键字变量可以散布 - 并且定义两者可以优先考虑其中之一(或导致错误)
  • 可以创建仅限关键字的无位置变量,以及无名的仅限位置的变量
  • 可以通过解析字符串来完成相当详细的表构造
  • 如果使用以下内容调用函数,则可以逐字使用参数列表1 table

用于编写参数转换器的一些有用函数是 unpack (在 5.2 中移至 table.unpack)、setfenv (在 5.2 中已弃用,新版本已弃用) _ENV 构造)和 select(从给定参数列表中返回单个值,或使用 '#' 返回列表的长度) 。

If you want named arguments and default values like PHP or Python, you can call your function with a table constructor:

myfunction{a,b=3,c=2}

(This is seen in many places in Lua, such as the advanced forms of LuaSocket's protocol modules and constructors in IUPLua.)

The function itself could have a signature like this:

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

Any values missing from the table of parameters will be taken from the __index table in its metatable (see the documentation on metatables).

Of course, more advanced parameter styles are possible using table constructors and functions- you can write whatever you need. For example, here is a function that constructs a function that takes named-or-positional argument tables from a table defining the parameter names and default values and a function taking a regular argument list.

As a non-language-level feature, such calls can be changed to provide new behaviors and semantics:

  • Variables could be made to accept more than one name
  • Positional variables and keyword variables can be interspersed - and defining both can give precedence to either (or cause an error)
  • Keyword-only positionless variables can be made, as well as nameless position-only ones
  • The fairly-verbose table construction could be done by parsing a string
  • The argument list could be used verbatim if the function is called with something other than 1 table

Some useful functions for writing argument translators are unpack (moving to table.unpack in 5.2), setfenv (deprecated in 5.2 with the new _ENV construction), and select (which returns a single value from a given argument list, or the length of the list with '#').

蒗幽 2024-11-14 14:14:13

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

In my opinion there isn't another way. That's just the Lua mentality: no frills, and except for some syntactic sugar, no redundant ways of doing simple things.

挖个坑埋了你 2024-11-14 14:14:13

从技术上讲,有 b = b == nil 和 7 或 b(应在 false 是有效值 false 或 7 的情况下使用) code> 的计算结果为 7),但这可能不是您想要的。

Technically, there's b = b == nil and 7 or b (which should be used in the case where false is a valid value as false or 7 evaluates to 7), but that's probably not what you're looking for.

三岁铭 2024-11-14 14:14:13

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

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()

The only way i've found so far that makes any sense is to do something like this:

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()
说不完的你爱 2024-11-14 14:14:13

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

参数列表

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

  1. 如果您期望真值(而不是 nilfalse),则使用 or 运算符。在这种情况下,即使给出 false 也可能会默认为某些内容。
  2. 使用显式 nil 检查 param == nil,用作 if param == nil then param = default end 或典型的 Lua 三元构造param == nil 和默认值或 param

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

function default(value, default_value)
    if value == nil then return default_value end
    return value
end

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

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

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)

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

f(nil)
f(nil, 2, 3)
f(nil, 2, nil, 4)
f(1, 2, 3, nil)

Varargs

Lua 的一个鲜为人知的功能是能够实际确定传递了多少个参数,包括区分显式传递的 nil 的能力code> 参数和“无参数”通过 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

警告:(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+)中)可用于将表转换为 vararg,可以将其视为参数 列表;但请注意,在 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}

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

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}

mix &可以匹配:

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}

元表

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

除了

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

可能的垃圾参数表之外,这还会在每次调用函数时创建 (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

避免元表

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

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

这将更改 params 表,正确设置默认值;用作params =complete(params, defaults)。同样,请记住将 defaults 表拖出函数。

As always, "Lua gives you the power, you build the mechanisms". The first distinction to make here is that between named parameters and the commonly used parameter list.

The parameter list

Assuming all your args are given in the parameter list as follows, they will all be initialized. At this point, you can't distinguish between "wasn't passed" and "was passed as nil" - both will simply be nil. Your options for setting defaults are:

  1. Using the or operator if you expect a truthy value (not nil or false). Defaulting to something even if false is given might be a feature in this case.
  2. Using an explicit nil check param == nil, used either as if param == nil then param = default end or the typical Lua ternary construct param == nil and default or param.

If you find yourself frequently repeating the patterns from point (2), you might want to declare a function:

function default(value, default_value)
    if value == nil then return default_value end
    return value
end

(whether to use global or local scope for this function is another issue I won't get into here).

I've included all three ways the following example:

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)

note that this also allows omitting arguments inbetween; trailing nil arguments will also be treated as absent:

f(nil)
f(nil, 2, 3)
f(nil, 2, nil, 4)
f(1, 2, 3, nil)

Varargs

A lesser known feature of Lua is the ability to actually determine how many arguments were passed, including the ability to distinguish between explicitly passed nil arguments and "no argument" through the select function. Let's rewrite our function using this:

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

Caveat: (1) the argument list got dragged into the function, hurting readability (2) this is rather cumbersome to write manually, and should probably be abstracted away, perhaps time using a wrapper function wrap_defaults({1, 2, 3, 4}, f) that supplies the defaults as appropriate. Implementation of this is left up to the reader as an exercise (hint: the straightforward way would first collect the args into a garbage table, then unpack that after setting the defaults).

Table calls

Lua provides syntactic sugar for calling functions with a single table as the only argument: f{...} is equivalent to f({...}). Furthermore, {f(...)} can be used to capture a vararg returned by f (caveat: if f returns nils, the table will have holes in it's list part).

Tables also allow implementing named "arguments" as table fields: Tables allow mixing a list and a hash part, making f{1, named_arg = 2} perfectly valid Lua.

In terms of limitations, the advantage of table call is that it only leaves a single argument - the table - on the stack rather than multiple arguments. For recursive functions, this allows hitting the stack overflow later. Since PUC Lua drastically increased the stack limit to ~1M this isn't much of an issue anymore; LuaJIT still has a stack limit of ~65k however, and PUC Lua 5.1 is even lower at around 15k.

In terms of performance & memory consumption, the table call is obviously worse: It requires Lua to build a garbage table, which will then waste memory until the GC gets rid of it. Garbage parameter tables should therefore probably not be used in hotspots where plenty of calls happen. Indexing a hashmap is also obviously slower than getting values straight off the stack.

That said, let's examine the ways to implement defaults for tables:

Unpacking / Destructuring

unpack (table.unpack in later versions (5.2+)) can be used to convert a table into a vararg, which can be treated like a parameter list; note however that in Lua the list part can't have trailing nil values, not allowing you to distinguish "no value" and nil. Unpacking / destructuring to locals also helps performance since it gets rid of repeated table indexing.

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}

if you use named fields, you'll have to explicitly destructure those:

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}

mix & match is possible:

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}

Metatables

The __index metatable field can be used to set a table which is indexed with name if params.name is nil, providing defaults for nil values. One major drawback of setting a metatable on a passed table is that the passed table's metatable will be lost, perhaps leading to unexpected behavior on the caller's end. You could use getmetatable and setmetatable to restore the metatable after you're done operating with the params, but that would be rather dirty, hence I would recommend against it.

Bad

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

in addition to the presumably garbage params table, this will create (1) a garbage metatable and (2) a garbage default table every time the function is called. This is pretty bad. Since the metatable is constant, simply drag it out of the function, making it an upvalue:

Okay

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

Avoiding metatables

If you want a default table without the hackyness of metatables, consider once again writing yourself a helper function to complete a table with default values:

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

this will change the params table, properly setting the defaults; use as params = complete(params, defaults). Again, remember to drag the defaults table out of the function.

┊风居住的梦幻卍 2024-11-14 14:14:13

如果您的函数不希望将布尔值 falsenil 作为参数值传递,那么您建议的方法就很好:

function test1(param)
  local default = 10
  param = param or default
  return param
end

--[[
test1(): [10]
test1(nil): [10]
test1(true): [true]
test1(false): [10]
]]

如果您的函数允许布尔值 false,但不是 nil,要作为参数值传递,您可以检查是否存在 nil,如 Stuart P. Bentley,只要默认值不是布尔值 false

function test2(param)
  local default = 10
  param = (param == nil and default) or param
  return param
end

--[[
test2(): [10]
test2(nil): [10]
test2(true): [true]
test2(false): [false]
]]

上述方法在以下情况下会中断默认值为布尔值 false

function test3(param)
  local default = false
  param = (param == nil and default) or param
  return param
end

--[[
test3(): [nil]
test3(nil): [nil]
test3(true): [true]
test3(false): [false]
]]

有趣的是,颠倒条件检查的顺序确实允许布尔值 false 成为默认值,并且名义上性能更高:

function test4(param)
  local default = false
  param = param or (param == nil and default)
  return param
end

--[[
test4(): [false]
test4(nil): [false]
test4(true): [true]
test4(false): [false]
]]

这种方法的工作原理似乎违反直觉,直到进一步检查,发现它们有点聪明。

如果您希望函数的默认参数允许传递 nil 值,则需要做一些更丑陋的事情,例如使用可变参数:

function test5(...)
  local argN = select('#', ...)
  local default = false
  local param = default
  if argN > 0 then
    local args = {...}
    param = args[1]
  end
  return param
end

--[[
test5(): [false]
test5(nil): [nil]
test5(true): [true]
test5(false): [false]
]]

当然,可变参数完全阻止使用函数参数的自动完成和 linting。

If your function expects neither Boolean false nor nil to be passed as parameter values, your suggested approach is fine:

function test1(param)
  local default = 10
  param = param or default
  return param
end

--[[
test1(): [10]
test1(nil): [10]
test1(true): [true]
test1(false): [10]
]]

If your function allows Boolean false, but not nil, to be passed as the parameter value, you can check for the presence of nil, as suggested by Stuart P. Bentley, as long as the default value is not Boolean false:

function test2(param)
  local default = 10
  param = (param == nil and default) or param
  return param
end

--[[
test2(): [10]
test2(nil): [10]
test2(true): [true]
test2(false): [false]
]]

The above approach breaks when the default value is Boolean false:

function test3(param)
  local default = false
  param = (param == nil and default) or param
  return param
end

--[[
test3(): [nil]
test3(nil): [nil]
test3(true): [true]
test3(false): [false]
]]

Interestingly, reversing the order of the conditional checks does allow Boolean false to be the default value, and is nominally more performant:

function test4(param)
  local default = false
  param = param or (param == nil and default)
  return param
end

--[[
test4(): [false]
test4(nil): [false]
test4(true): [true]
test4(false): [false]
]]

This approach works for reasons that seem counter-intuitive until further examination, upon which they are discovered to be kind of clever.

If you want default parameters for functions that do allow nil values to be passed, you'll need to do something even uglier, like using variadic parameters:

function test5(...)
  local argN = select('#', ...)
  local default = false
  local param = default
  if argN > 0 then
    local args = {...}
    param = args[1]
  end
  return param
end

--[[
test5(): [false]
test5(nil): [nil]
test5(true): [true]
test5(false): [false]
]]

Of course, variadic parameters completely thwart auto-completion and linting of function parameters in functions that use them.

玉环 2024-11-14 14:14:13

简短的回答是这是最简单也是最好的方法。在 lua 中,变量默认等于 nil 。这意味着如果我们不向 lua 函数传递参数,则参数将退出但为 nil,并且 lua 程序员使用此 lua 属性来设置默认值。

这也不是设置默认值的方法,但您可以使用以下函数,

此函数会创建一个错误,因为您没有将值传递给参数

function myFn(arg1 , arg2)
err = arg1 and arg2
if not err then error("argument") end
-- or
if not arg1 and arg2 then error("msg") end

但这不是一个好方法,最好不要使用此函数

并在图中显示 [,arg] 中的可选参数

function args(a1 [,a2])
-- some
end
function args ( a1 [,a2[,a3]])
-- some
end

Short answer is that it's simplest and best way . in lua , variables by default equal with nil . this means if we don't pass argument to lua functions ,the argument is exits but is nil and lua programmers uses of this lua attribute for set the default value .

also it's not a way for set default value but you can use following function

this function create a error is you don't pass values to arguments

function myFn(arg1 , arg2)
err = arg1 and arg2
if not err then error("argument") end
-- or
if not arg1 and arg2 then error("msg") end

but it's not a good way and better is don't use of this function

and in diagrams shows optional argument in [,arg]

function args(a1 [,a2])
-- some
end
function args ( a1 [,a2[,a3]])
-- some
end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文