定义函数参数的默认值
在 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 方式不起作用,我只是想知道这是否是唯一的方法。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
如果你想要像 PHP 或 Python 这样的命名参数和默认值,你可以使用表构造函数来调用你的函数:(
这在 Lua 中的很多地方都可以看到,例如 LuaSocket 协议模块的高级形式 和 IUPLua。)
函数本身可以有这样的签名:
参数表中缺少的任何值都将从其元表中的
__index
表中获取(请参阅元表文档)。当然,使用表构造函数和函数可以实现更高级的参数样式 - 您可以编写您需要的任何内容。例如,这里是一个函数,它构造一个从表中获取命名或位置参数表的函数定义参数名称和默认值以及采用常规参数列表的函数。
作为一项非语言级功能,可以更改此类调用以提供新的行为和语义:
用于编写参数转换器的一些有用函数是
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:
(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:
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:
Some useful functions for writing argument translators are
unpack
(moving totable.unpack
in 5.2),setfenv
(deprecated in 5.2 with the new_ENV
construction), andselect
(which returns a single value from a given argument list, or the length of the list with'#'
).在我看来,没有其他办法了。这就是 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.
从技术上讲,有
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 wherefalse
is a valid value asfalse or 7
evaluates to 7), but that's probably not what you're looking for.到目前为止我发现的唯一有意义的方法就是做这样的事情:
The only way i've found so far that makes any sense is to do something like this:
一如既往,“Lua 给你力量,你构建机制”。这里要做的第一个区别是命名参数和常用的参数列表。
参数列表
假设您的所有参数都在参数列表中给出,如下所示,它们都会被初始化。此时,您无法区分“未通过”和“作为
nil
传递” - 两者都只是nil
。设置默认值的选项有:nil
或false
),则使用or
运算符。在这种情况下,即使给出false
也可能会默认为某些内容。nil
检查param == nil
,用作if param == nil then param = default end
或典型的 Lua 三元构造param == nil 和默认值或 param
。如果您发现自己经常重复第 (2) 点中的模式,您可能需要声明一个函数:(
该函数是否使用全局作用域或局部作用域是另一个问题,我不会在这里讨论)。
我在以下示例中包含了所有三种方法:
请注意,这也允许省略中间的参数;尾随 nil 参数也将被视为不存在:
Varargs
Lua 的一个鲜为人知的功能是能够实际确定传递了多少个参数,包括区分显式传递的 nil 的能力code> 参数和“无参数”通过
select
功能。让我们用这个重写我们的函数:
警告:(1)参数列表被拖入函数中,损害了可读性(2)手动编写相当麻烦,并且可能应该被抽象掉,也许需要使用包装函数的时间
wrap_defaults({1, 2, 3, 4}, f)
提供适当的默认值。这个实现留给读者作为练习(提示:直接的方法是首先将参数收集到垃圾表中,然后在设置默认值后将其解压)。表调用
Lua 提供了以单个表作为唯一参数来调用函数的语法糖:
f{...}
等价于f({...})
。此外,{f(...)}
可用于捕获f
返回的可变参数(注意:如果f
返回nil
s,表格的列表部分将有漏洞)。表还允许实现命名“参数”作为表字段:表允许混合列表和散列部分,使
f{1,named_arg = 2}
完全有效的Lua。就限制而言,表调用的优点是它只在堆栈上留下单个参数(表),而不是多个参数。对于递归函数,这允许稍后遇到堆栈溢出。由于 PUC Lua 将堆栈限制大幅增加到约 1M,这不再是一个问题;然而,LuaJIT 的堆栈限制仍然约为 65k,而 PUC Lua 5.1 甚至更低,约为 15k。
在性能方面内存消耗,表调用显然更糟糕:它需要 Lua 构建一个垃圾表,然后垃圾表会浪费内存,直到 GC 清除它。因此,垃圾参数表不应该在有大量内存的热点中使用。发生通话。对哈希图进行索引显然也比直接从堆栈中获取值要慢。
也就是说,让我们检查一下实现表默认值的方法:
解包/解构
unpack
(table.unpack
在更高版本(5.2+)中)可用于将表转换为 vararg,可以将其视为参数 列表;但请注意,在 Lua 中,列表部分不能有尾随 nil 值,不允许您区分“无值”和 nil。对局部变量进行解包/解构也有助于提高性能,因为它消除了重复的表索引。如果您使用命名字段,则必须显式地解构这些字段:
mix &可以匹配:
元表
如果
params.name
为,则
,提供__index
元表字段可用于设置使用name
索引的表nilnil
值的默认值。在传递的表上设置元表的一个主要缺点是传递表的元表将会丢失,可能会导致调用方出现意外的行为。在完成对params
的操作后,您可以使用getmetatable
和setmetatable
来恢复元表,但这会很脏,因此我会建议反对它。除了
可能的垃圾参数表之外,这还会在每次调用函数时创建 (1) 垃圾元表和 (2) 垃圾默认表。这很糟糕。由于元表是常量,只需将其拖出函数,使其成为上值:
好的,
避免元表
如果您想要一个没有元表的老套的默认表,请考虑再次为自己编写一个辅助函数来完成具有默认值的表:
这将更改
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 benil
. Your options for setting defaults are:or
operator if you expect a truthy value (notnil
orfalse
). Defaulting to something even iffalse
is given might be a feature in this case.nil
checkparam == nil
, used either asif param == nil then param = default end
or the typical Lua ternary constructparam == nil and default or param
.If you find yourself frequently repeating the patterns from point (2), you might want to declare a function:
(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:
note that this also allows omitting arguments inbetween; trailing
nil
arguments will also be treated as absent: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 theselect
function. Let's rewrite our function using this: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 tof({...})
. Furthermore,{f(...)}
can be used to capture a vararg returned byf
(caveat: iff
returnsnil
s, 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 trailingnil
values, not allowing you to distinguish "no value" andnil
. Unpacking / destructuring to locals also helps performance since it gets rid of repeated table indexing.if you use named fields, you'll have to explicitly destructure those:
mix & match is possible:
Metatables
The
__index
metatable field can be used to set a table which is indexed withname
ifparams.name
isnil
, providing defaults fornil
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 usegetmetatable
andsetmetatable
to restore the metatable after you're done operating with theparams
, but that would be rather dirty, hence I would recommend against it.Bad
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
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:
this will change the
params
table, properly setting the defaults; use asparams = complete(params, defaults)
. Again, remember to drag thedefaults
table out of the function.如果您的函数不希望将布尔值
false
或nil
作为参数值传递,那么您建议的方法就很好:如果您的函数允许布尔值
false
,但不是nil
,要作为参数值传递,您可以检查是否存在nil
,如 Stuart P. Bentley,只要默认值不是布尔值false
:上述方法在以下情况下会中断默认值为布尔值
false
:有趣的是,颠倒条件检查的顺序确实允许布尔值
false
成为默认值,并且名义上性能更高:这种方法的工作原理似乎违反直觉,直到进一步检查,发现它们有点聪明。
如果您希望函数的默认参数允许传递 nil 值,则需要做一些更丑陋的事情,例如使用可变参数:
当然,可变参数完全阻止使用函数参数的自动完成和 linting。
If your function expects neither Boolean
false
nornil
to be passed as parameter values, your suggested approach is fine:If your function allows Boolean
false
, but notnil
, to be passed as the parameter value, you can check for the presence ofnil
, as suggested by Stuart P. Bentley, as long as the default value is not Booleanfalse
:The above approach breaks when the default value is Boolean
false
:Interestingly, reversing the order of the conditional checks does allow Boolean
false
to be the default value, and is nominally more performant: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:Of course, variadic parameters completely thwart auto-completion and linting of function parameters in functions that use them.
简短的回答是这是最简单也是最好的方法。在 lua 中,变量默认等于 nil 。这意味着如果我们不向 lua 函数传递参数,则参数将退出但为 nil,并且 lua 程序员使用此 lua 属性来设置默认值。
这也不是设置默认值的方法,但您可以使用以下函数,
此函数会创建一个错误,因为您没有将值传递给参数
但这不是一个好方法,最好不要使用此函数
并在图中显示 [,arg] 中的可选参数
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 isnil
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
but it's not a good way and better is don't use of this function
and in diagrams shows optional argument in [,arg]