Dictionary to variables in julia

Hey,
I will keep the question simple, so I have a dictionary like this:

my_dict = Dict(“dataFolder”=> “my_data”, “tile_size” => [100, 200], “dims” => 20)

and I want the keys to be variables as in my workspace with the corresponding dictionary values:

dataFolder = “my_data”
tile_size = [100, 200]
dims = 20

My key values are always a string, but the corresponding value may change.

this is ~bad practice in Julia in general, maybe it’s more common in R or something.

can you describe why you need to do this exactly?

3 Likes

@jling I’m reading a json file that user writes lots of parameters and my Julia program will parse the json and use the parameters as variables.

can’t you just use that json?

2 Likes

yes.

my_dict[“dataFolder”]

would perfectly work but I thought dataFolder as a variable might be more aesthetic? but you are not suggesting, I see. Then I think I will stick with my dictionary.

You could make it look a bit more aesthetic by defining a getproperty method that gives you slightly nicer my_dict.dataFolder syntax instead of my_dict["dataFolder"].

Some limitations of that approach:

  • you need my_dict to be a type that you’ve defined yourself otherwise defining a
    Base.getproperty(d::Dict, s::Symbol) = ...
    
    is definitely type piracy since you own neither Dict, Symbol, nor getproperty. So you’d need to wrap Dict in your own type:
     struct MyDict
        dict::Dict
     end
     Base.getproperty(d::MyDict, s::Symbol) = getfield(d, :dict)[string(s)]
    
  • The keys of the Dict must be valid Julia identifiers, so no dashes, spaces, etc.

You can make values stored in my_dict into variables if you use eval:

mod = Module()
for (k, v) in my_dict
    Core.eval(mod, :($(Symbol(k)) = $v))
end
@assert mod.dataFolder == "my_data"

But as @jling mentioned above, generally this is not what you should be doing, and in this example doesn’t actually provide any benefit over the getproperty approach. It has it’s place at times, but those are pretty limited, and really a last resort.

Another way may be with keyword arguments like:

julia> function use_variables(;
           tile_size = error("tile_size not provided"),
           dataFolder = error("dataFolder not provided"),
           dims = error("dims not provided"),
           kws... # ignore anything else.
       )
           @show tile_size dataFolder dims # use as variables inside function.
           # ...
       end

julia> use_variables(; (Symbol(k)=>v for (k,v) in my_dict)...)
tile_size = [100, 200]
dataFolder = "my_data"
dims = 20
20

Which might work if you only need a predefined set of variables to be handled.

1 Like

Don’t do this. It is bad programming practice in any language.

2 Likes

Hi, just necroposting to say that something like this is easy with the new destructuring syntax. Just do:

(; dataFolder, tile_size, dims) = my_dict

It’s also not bad practice because you are still manually specifying which variables to create. The only issue might that there will be type instability, as the types of the values of my_dict are not in general inferred.

https://github.com/mauro3/UnPack.jl

An alternative to creating a new type and defining getproperty is to convert the Dict to a NamedTuple:

julia> params = (; [Symbol(k) => v for (k, v) in pairs(my_dict)]... )
(tile_size = [100, 200], dataFolder = "my_data", dims = 20)

julia> params.tile_size
2-element Vector{Int64}:
 100
 200

julia> params.dataFolder
"my_data"

julia> params.dims
20

Alternatively, if your Dict can be nested (have other Dicts as values), for example:

my_dict_nested = Dict("tile_size" => [100, 200], "dims" => 20, "coords" => Dict("x" => 0, "y" => 1, "z" => 2))

You can define a simple function to convert the Dict recursively to a NamedTuple:

julia> named_tuple(dict::AbstractDict) = (; [Symbol(k) => named_tuple(v) for (k, v) in pairs(dict) ]... )
named_tuple (generic function with 1 method)

julia> named_tuple(x) = x
named_tuple (generic function with 2 methods)

And then you can use it like:

julia> params_nested = named_tuple(my_dict_nested)
(tile_size = [100, 200], dims = 20, coords = (x = 0, z = 2, y = 1))

julia> params_nested.tile_size
2-element Vector{Int64}:
 100
 200

julia> params_nested.coords.x
0

julia> params_nested.coords.y
1
2 Likes

This is a nice idea. Personally I also much prefer using a.x instead of a["x"].
I have used this in the past but I was never sure if this is something that one needs to have a bit of caution with do if one does not know that the size of the dict will be small.
Since large tuples put a lot of stress on the compiler, this might become problematic as well right?
I wonder if anyone has some wisdom about this.

1 Like