首页 > 解决方案 > 如何从 splatted kwargs 字段中检索关键字参数?

问题描述

如果我有一个函数签名f(args...; kwargs...),我怎样才能得到一个特定的关键字kwargs呢?天真的打字 kwargs.x不起作用:

julia> f(args...; kwargs...) = kwargs.x
f (generic function with 1 method)

julia> f(x=1)
ERROR: type Pairs has no field x
Stacktrace:
 [1] getproperty(::Base.Iterators.Pairs{Symbol,Int64,Tuple{Symbol},NamedTuple{(:x,),Tuple{Int64}}}, ::Symbol) at ./Base.jl:20
 [2] #f#7(::Base.Iterators.Pairs{Symbol,Int64,Tuple{Symbol},NamedTuple{(:x,),Tuple{Int64}}}, ::typeof(f)) at ./REPL[2]:1
 [3] (::var"#kw##f")(::NamedTuple{(:x,),Tuple{Int64}}, ::typeof(f)) at ./none:0
 [4] top-level scope at REPL[3]:1

这个问题出现在#helpdesk 的 JuliaLang Slack 频道上。要自动邀请非常有用的 julia slack,只需填写https://slackinvite.julialang.org

标签: julia

解决方案


发生这种情况的原因是默认情况下,splatted 关键字参数不存储在命名元组中。我们可以看到它们是如何存储的:

julia> g(;kwargs...) = kwargs
g (generic function with 1 method)

julia> g(a=1)
pairs(::NamedTuple) with 1 entry:
  :a => 1

julia> g(a=1) |> typeof
Base.Iterators.Pairs{Symbol,Int64,Tuple{Symbol},NamedTuple{(:a,),Tuple{Int64}}}

因此,splatted kwargs 被存储为某种迭代器对象。但是,我们可以像这样轻松地将该kwargs迭代器转换为 NamedTuple:(;kwargs...)然后以我们期望的方式访问它,因此您的示例将转换为

julia> f(args...; kwargs...) = (;kwargs...).x
f (generic function with 1 method)

julia> f(x=1, y=2)
1

当然,更惯用的方法是将函数编写为

julia> f(args...; x, kwargs...) = x
f (generic function with 1 method)

julia> f(x=1, y=2)
1

但这假设您在编写函数时知道要访问的名称 ( x)。


一个简短的旁注:如果我们回到我们的示例g(;kwargs...) = kwargs,我们可以询问返回的迭代器对象的字段名,如下所示:

julia> g(x=1, y=2) |> typeof |> fieldnames
(:data, :itr)

嗯,这个data领域是什么?

julia> g(x=1, y=2).data
(x = 1, y = 2)

啊哈!所以我们实际上可以使用它来将 kwargs 作为命名元组,即f(;kwargs...) = kwargs.data.x可以工作,但我不推荐这种方法,因为它似乎依赖于未记录的行为,所以它可能只是一个不能保证稳定的实现细节跨朱莉娅版本。


推荐阅读