Wie rufe ich Schlüsselwortargumente aus einem Feld mit bespritzten Warwar ab?

9

f(args...; kwargs...)Wie kann ich ein bestimmtes Schlüsselwort verwenden, wenn ich eine Funktionssignatur wie habe kwargs? Naives Tippen kwargs.xfunktioniert nicht:

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

Diese Frage erschien auf dem JuliaLang Slack-Kanal im #helpdesk. Für eine automatische Einladung zu der sehr hilfreichen Julia Slack füllen Sie einfach https://slackinvite.julialang.org aus

Mason
quelle

Antworten:

10

Der Grund dafür ist, dass Splatted-Keyword-Argumente standardmäßig nicht in einem benannten Tupel gespeichert werden. Wir können sehen, wie sie so gespeichert sind:

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

Die bespritzten Warwar werden stattdessen als eine Art Iteratorobjekt gespeichert. Wir können diesen kwargsIterator jedoch problemlos wie folgt in ein NamedTuple konvertieren : (;kwargs...)und dann wie erwartet darauf zugreifen, sodass Ihr Beispiel in übersetzt wird

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

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

Der idiomatischere Weg, dies zu tun, wäre natürlich, stattdessen die Funktion als zu schreiben

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

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

Dies setzt jedoch voraus, dass Sie den Namen kennen, auf den Sie zugreifen möchten ( x), wenn Sie die Funktion schreiben.


Eine kurze Randnotiz: Wenn wir zu unserem Beispiel von zurückkehren g(;kwargs...) = kwargs, können wir nach den Feldnamen des Iteratorobjekts fragen, das wie folgt zurückgegeben wurde:

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

Hm, was ist das für ein dataFeld?

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

Aha! Damit können wir die kwargs tatsächlich als benanntes Tupel erhalten, dh es f(;kwargs...) = kwargs.data.xwürde funktionieren, aber ich würde diesen Ansatz nicht empfehlen, da er auf undokumentiertem Verhalten zu beruhen scheint, sodass es sich möglicherweise nur um ein Implementierungsdetail handelt, dessen Stabilität nicht garantiert ist über Julia Versionen.

Mason
quelle