Как я могу получить аргументы ключевых слов из поля разделенных кваргами?

9

Если у меня есть подпись функции, например 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

Этот вопрос появился на канале JuliaLang Slack в #helpdesk. Чтобы автоматически пригласить очень полезную Джулию Слак, просто заполните https://slackinvite.julialang.org

каменщик
источник

Ответы:

10

Причина, по которой это происходит, заключается в том, что аргументы с разделенными ключевыми словами по умолчанию не хранятся в именованном кортеже. Мы можем видеть, как они хранятся так:

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

Таким образом, выделенные кварги вместо этого хранятся как некий объект итератора. Тем не менее, мы можем легко преобразовать этот 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будет работать, но я бы не рекомендовал этот подход, так как он, кажется, полагается на недокументированное поведение, так что это может быть простой деталью реализации, которая не гарантируется стабильной по версиям джулия.

каменщик
источник