¿Cómo recupero argumentos de palabras clave de un campo de kwargs salpicados?

9

Si tengo una firma de función como f(args...; kwargs...), ¿cómo puedo obtener una palabra clave específica kwargs? Escribir ingenuamente kwargs.xno funciona:

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

Esta pregunta apareció en el canal JuliaLang Slack en el #helpdesk. Para una invitación automática a la muy útil julia slack, simplemente complete https://slackinvite.julialang.org

Masón
fuente

Respuestas:

10

La razón por la que esto sucede es que los argumentos de palabras clave salpicados no se almacenan en una tupla con nombre de forma predeterminada. Podemos ver cómo se almacenan así:

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}}}

Por lo tanto, los kwargs salpicados se almacenan como algún tipo de objeto iterador. Sin embargo, podemos convertir fácilmente ese kwargsiterador en un NamedTuple de esta manera: (;kwargs...)y luego acceder a él de la manera que esperaríamos, por lo que su ejemplo se traduciría en

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

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

Por supuesto, la forma más idiomática de hacer esto sería escribir la función como

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

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

pero esto supone que conoce el nombre al que desea acceder ( x) en el momento en que escribe la función.


Una breve nota al margen: si volvemos a nuestro ejemplo de g(;kwargs...) = kwargs, podemos pedir los nombres de campo del objeto iterador que se devolvió de la siguiente manera:

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

¿Qué es este datacampo?

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

¡Ajá! así que en realidad podemos obtener los kwargs como una tupla nombrada usando eso, es decir f(;kwargs...) = kwargs.data.x, funcionaría, pero no recomendaría este enfoque ya que parece depender de un comportamiento indocumentado, por lo que puede ser un mero detalle de implementación que no se garantiza que sea estable a través de las versiones de julia.

Masón
fuente