Jak odzyskać argumenty słów kluczowych z pola splatted kwargs?

9

Jeśli mam sygnaturę funkcji f(args...; kwargs...), jak mogę uzyskać określone słowo kluczowe kwargs? Naiwne pisanie kwargs.xnie działa:

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

To pytanie pojawiło się na kanale JuliaLang Slack na #helpdesk. Aby automatycznie zaprosić do bardzo pomocnej Julii Slack, wystarczy wypełnić https://slackinvite.julialang.org

Mason
źródło

Odpowiedzi:

10

Dzieje się tak dlatego, że argumenty słów kluczowych nie są domyślnie przechowywane w nazwanej krotce. Widzimy, jak są przechowywane:

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

Tak więc splatted kwargs są zamiast tego przechowywane jako jakiś obiekt iteratora. Możemy jednak łatwo przekonwertować ten kwargsiterator na NamedTuple w taki sposób: (;kwargs...)a następnie uzyskać do niego dostęp w oczekiwany sposób, aby Twój przykład przełożył się na

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

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

Oczywiście, bardziej idiomatycznym sposobem na zrobienie tego byłoby zapisanie funkcji jako

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

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

ale zakłada to, że znasz nazwę, do której chcesz uzyskać dostęp ( x) w momencie pisania funkcji.


Krótka informacja: jeśli wrócimy do naszego przykładu g(;kwargs...) = kwargs, możemy poprosić o nazwy pól obiektu iteratora, który został zwrócony w następujący sposób:

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

Hm, co to za datapole?

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

Aha! więc możemy faktycznie uzyskać kwargs jako nazwaną krotkę za pomocą tego, tj. f(;kwargs...) = kwargs.data.xdziałałoby, ale nie poleciłbym tego podejścia, ponieważ wydaje się, że opiera się na nieudokumentowanym zachowaniu, więc może to być jedynie szczegół implementacji, który nie gwarantuje stabilności w wersjach Julii.

Mason
źródło