Julia vs. Python: **kwargs


#1

I’m looking into interfacing Julia to another language with different rules for identifiers. Specifically, e.g., “m.R” would be a valid identifier in this other language. In the past, I’ve worked with this problem for Python. In Python, suppose I define a function:

def my_func (** kwargs):
  return kwarg

then I get the following behavior:

my_func (a=2, b=3)

leads to {’a’: 2, ’b’: 3}, and

D = {"a":2, "b": 3}
my_func(**D)

leads to {’a’: 2, ’b’: 3} .

Similarly, my_func(m.R = 8.31) crashes in Python – because “m.R” is an illegal identifier, while:

D = {"m.R":8.31}
my_func(**D)

gives the desired result: {'m.R':8.31}.

Thus: for those identifiers in this other language that are valid Python (or: Julia) identifiers, I can use keyword assignment when calling the function. For those identifiers that are invalid Python identifiers, I can resort to this “dirty” trick of specifying the identifiers as dictionary keys (where any string is valid).

Is there a similar trick in Julia?

-B


#2

You can get the *args, and ***kwargs in Julia by doing

function my_func(args... ; kwargs...)
end

which will fill kwargs with a dictionary like you would expect. I am not sure you can do the trick with the string keys as kwargs keys must be a Symbol so the dotted part is not legal. I am sure some macro tricks could get around this, but likely better to just use an underscore instead of a dot.


#3

Note that the type of kwargs will be different between Julia 0.6 and master:

getkwargs(; kwargs...) = kwargs

On 0.6:

julia> getkwargs(a = 1, b = 2)
2-element Array{Any,1}:
 (:a, 1)
 (:b, 2)

On master:

julia> getkwargs(a = 1, b = 2)
pairs(::NamedTuple) with 2 entries:
  :a => 1
  :b => 2

#4

As tkoolen already answered: Symbols may contain a lot of invalid julia identifiers. On 0.62:

bar(; kwargs...)=@show kwargs
julia> bar(; kwargs...)=@show kwargs;
julia> bar(a=1, b=2);
kwargs = Any[(:a, 1), (:b, 2)]
julia> bar(;(:a,1), (Symbol("b"),2), (Symbol("a.b = 54"),3));
kwargs = Any[(:a, 1), (:b, 2), (Symbol("a.b = 54"), 3)]
julia> Symbol("foo\0bar")
ERROR: ArgumentError: Symbol name may not contain \0

#5

Note that this example throws an error on Julia master.


#6

I don’t know if this will work with your interface, but you might use Pairs

On a recent version of Julia v0.7 …

julia> f(;kwargs...) = @show kwargs
f (generic function with 1 method)

julia> f(:a=3)
ERROR: syntax: keyword argument is not a symbol: ":a"

julia> g(kwargs::Pair...) = @show kwargs
g (generic function with 1 method)

julia> g(:a => 1)
kwargs = (:a=>1,)
(:a=>1,)

julia> g(:a => 1, Symbol("R.m") => 2)
kwargs = (:a=>1, Symbol("R.m")=>2)
(:a=>1, Symbol("R.m")=>2)

#7

OK…

function my_func(;kwargs...)
return kwargs
end

leads to my_func(a=1,b=2) returns a dictionary. Which I guess implies that internally, the kwargs are interpreted as a Dict. But I also need to alternatively use a Dict as an argument, and have it come through the call in the form of a Dict.

Perhaps I need to have two methods for this, and rely on dispatching?

Regarding symbols, and, e.g., “m.R”… my understanding is that the syntax:

  • :(expression) really means an unevaluated expression, and it is only in the case that “expression” equals a valid identifier that this is equal to a symbol. In other words… :(m.R) is not a symbol.
  • in general, Symbol(string) creates a symbol for anything within the string, e.g., Symbol(“m.R”) gives the symbol.

#8

Ooops… my_func(a=1,b=2) returns an array of pairs, [(:a,1), (:b,2)] – more or less. Not a Dict…

On the other hand, Dict(my_func(a=1,b=2)) returns the Dict…


#9

The equivalent to python’s foo(**D) in Julia is foo(;D...) in which D is a collection of Pairs (a Dict with Symbol keys will work, as will a vector of Pair{Symbol, whatever}. For example, on Julia v0.7 master:

julia> f(;kw...) = kw
f (generic function with 1 method)

julia> f(hello=1, world=2)
pairs(::NamedTuple) with 2 entries:
  :hello => 1
  :world => 2

julia> D = [:hello => 1, :world => 2]
2-element Array{Pair{Symbol,Int64},1}:
 :hello => 1
 :world => 2

julia> f(;D...)
pairs(::NamedTuple) with 2 entries:
  :hello => 1
  :world => 2

julia> D = Dict([:hello => 1, :world => 2])
Dict{Symbol,Int64} with 2 entries:
  :hello => 1
  :world => 2

julia> f(;D...)
pairs(::NamedTuple) with 2 entries:
  :hello => 1
  :world => 2

and using this syntax you can pass arguments with keys that are not valid identifiers:

julia> f(;[:hello => 1, Symbol("@boo") => 2]...)
pairs(::NamedTuple) with 2 entries:
  :hello         => 1
  Symbol("@boo") => 2

#10

Thanks! Looks nice!

This doesn’t work in Julia 0.6.2, as far as I can see. But good to know that it is coming!


#11

Ooops, I did it again :-). It does work in Julia 0.6.2…

julia> my_func(;kw...)=kw

and:

julia> my_func(a=1,b=2)
2-element Array{Any,1}:
 (:a, 1)
 (:b, 2)

while:

julia> my_func(;[(:a,1),(:b,2)]...)
2-element Array{Any,1}:
 (:a, 1)
 (:b, 2)

and

julia> my_func(;Dict([(:a,1),(:b,2)])...)
2-element Array{Any,1}:
 (:a, 1)
 (:b, 2)

Finally:

julia> my_func(;[(Symbol("m.R"),8.31),(Symbol("diff(T,x)"),3)]...)
2-element Array{Any,1}:
 (Symbol("m.R"), 8.31)
 (Symbol("diff(T,x)"), 3)

So – I guess the change in Julia v. 0.7/1.0 will be that instead of 2-element array, the result will be pairs.


#12

So – essentially, in Julia v.0.6.2, using keyword assignment passes the arguments as a vector of pairs (tuple) of symbol + value. Right? Like in:

julia> c = my_func(a=1,b=2)

leads to the vector [(:a,1),(:b,2)], and I can convert the vector to Dict, say, d = Dict(c) and refer to the values as, e.g., d[:a]

While in Julia v.0.7, the result is a Named Tuple?? Does that mean that in Julia v. 0.7, if I do the same as above, I can directly refer to c[:a]?

Another thing… I tested the following in v.0.6.2:

julia> my_func(a=1,b=2,a=3)

with result [(:a,1), (:b,2), (:a,3)]. It is not clear to me how a function would treat this wrt. the value of :a.

-B


#13

I think so… see this:

julia> r = my_func(a=1,b=2)
pairs(::NamedTuple) with 2 entries:
  :a => 1
  :b => 2

julia> r[:a]
1

julia> r[:b]
2

Let’s try the duplicate key case. It’s fixed!

julia> r = my_func(a=1,b=2,a=3)
ERROR: syntax: keyword argument "a" repeated in call to "my_func"

Note: I’m using a nightly build that’s several weeks old.


#14

Thanks, tk3369!

Out of curiosity…

  • How does named tuples work in v.0.7? (The same way as in v.0.6.2? Or a simpler way? Do one still have to use the NamedTuples package? And the same macro system to define the named tuples?)

#15
julia> c = (a=3, b=2)
(a = 3, b = 2)

julia> c.a
3

julia> c.b
2

julia> c[:a]
3

julia> c[:b]
2

No package needed.


#16

Beautiful! But doesn’t work in v. 0.6.2… can’t wait for v. 0.7 (I’m not a solid computer scientist, so I’ll wait for the official release…)


#17

Ah. Yes, beautiful…

This is similar keyword assignment in function calls.

BUT: what if I have “keywords” that are not proper Julia identifiers? The same trick as with function calls?

kw = [Symbol("m.T") => 5]
ntup = (;kw...)
  1. If so, can elements be addressed as attribute, e.g, like c.b? How?
  2. If have symbols such as "US$" (interpolation $?) or "\frac{x}{y}" (escape symbols?), what then?

#18
julia> kw = [Symbol("us\$") => 5]
1-element Array{Pair{Symbol,Int64},1}:
 Symbol("us$") => 5

julia> ntup = (;kw...)
(us$ = 5,)

julia> getfield(ntup, Symbol("us\$"))
5

#19

Great. I love it when it is systematic :-).

Presumably also:

ntup[Symbol("us\$")]
5