Problem Statement:
How do I create a function that will pass a value that is determined upon creation of the function, and not upon calling the function?
Background
Using altair
I am trying to set up multiple themes, and in order to create a theme, I need to "register" it with code something like this:
def black_marks():
return {
'config': {
'mark': {
'color': 'black',
'fill': 'black'
}
}
}
# register the custom theme under a chosen name
alt.themes.register('black_marks', black_marks)
# enable the newly registered theme
alt.themes.enable('black_marks')
At this point, since 'black_marks'
is both registerd and enabled, any future use of altair
will include those defaults.
Notice that the function black_marks
returns a dict
, and it is the function which needs to be registered.
Issue
I have several themes that I would like to set up at once, so I have created a loop through these configurations something like this in my code:
for theme_name, theme_dict in mythemes.items():
themes.register(theme_name, lambda: theme_dict)
Then I discovered that the themes are not actually being registered at the time the themes.register
function is being called. Instead, it is handled "lazily".
For instance, let's say that the theme keys are: ['light', 'dark', 'paper']
. Once I have completed the loop above, I discovered that all 3 of these theme names are registered (I can alt.themes.enable('light')
, for instance), but they all point to the last one, paper
. This is happening because theme_dict
, in the final round, is indeed pointing to the theme associated with paper
.
Desire
What I would like to do is to be able to somehow "hard code" what theme_dict
is pointing to, so that themes.register
points to a function which contains the dict that was generated on each pass. But no matter how I have tried to think about this, I cannot have the lambda function create a function that is "set in stone". Instead, the function will just return whatever is in the last iteration.
So, while this is sort of an altair
-specific problem, I feel like the solution should be a generic one:
How do I create a function that will pass a value that is determined upon creation of the function, and not upon calling the function?
Fuller Example
As per requested in the comment, here is a fuller snapshot of my code:
for theme_key, theme_file in THEME_FILES.items(): # THEME_FILES is a dictionary with values containing file paths
with open(theme_file) as theme_fh:
raw_theme = toml.load(theme_fh)
dims = {
"width": raw_theme.pop("width"),
"height": raw_theme.pop("height"),
}
raw_theme["view"] = dims
final_theme["config"] = raw_theme
themes.register(theme_key, lambda: final_theme)
(Apologies for the poor variable names... I was frustrated with the problems I was having, and renaming thinking the issue was an accidental overwrite of global vars.)
Again, just to be clear, the theme_key
is proper registered. If, for instance, the different keys were ['light', 'dark', 'paper']
, then I can see all 3. But all three of them point to the theme made with the last iteration. In this case, the 'paper'
theme.
If I were to just iterate over 1 of the themes, it works perfectly fine. So I am fairly confident I have pinpointed the problem.