Closed kalekundert closed 2 years ago
Recently I've been thinking that it was a mistake to cache bound getters, but this is a case where I need them to be cached (and the cache isn't working right). Maybe this is a good opportunity to change the way I cache bound getters:
Bound getters need to be recalculated on loads, because of implicit keys.
My idea is to clear the bound getter cache every time a new value is calculated:
This does allow the possibility that the cache could get stale, but only if bound getters are loaded without calculating a new value. There's no reason this should ever happen. It just means that callers of param._load_bound_getters()
need to be careful.
This also means that the object won't store the bound getters in perpetuity, which is nice.
This is only inefficient for dynamic params, but most params aren't dynamic, and even for those that are, it's not a huge expense. I think it's worth the reduced memory footprint.
Alternatively, I could not cache the getters at all, and instead make them an optional argument to the param._update_value()
method. The default path would calculate them on the fly, while special paths like SharedKey could precalculate them...
Wait, no. If two shared keys need to operate on the same param, the second key needs to build on the bound getters prepared by the first. This requires having a short-term cache.
One thing that BYOC doesn't naturally handle well are config values that specify multiple parameters. For example:
Here, we need to do some string parsing on the config value to separate the hour and the minute. The result of this parsing, though, needs to be saved to two different parameters. There are three ways to handle this currently:
Just don't have two separate parameters. This is what I most often do, but I don't like that the structure of the config dictates the structure of the code. Often it makes more sense for the parameters to be separate, and this issue is the only reason they're combined.
Do the parsing twice. This works, but is inefficient.
Save the parsed value to a private parameter, then derive the public parameters from the private one. This also works, but requires that a temporary value be permanently stored in the object. In other words, it's not memory efficient. It's also pretty verbose.
Today I was thinking about a better way to handle this. My idea is to have cast functions that are shared between multiple parameters. Whenever the function is evaluated for one parameter, all of the others (if stale) are updated as well. Here's what it might look like:
Some thoughts:
Key
. But it's conceivable that I'd want to this sort of feature withMethod
orFunc
. Maybe I'd end up doing some sort of mixin thing, although I'd just start withKey
.parse_time
function. The0
and1
arguments specify which element of the tuple each parameter will get. These arguments could also be callables, e.g.attrgetter('x')
.param.__set_name__()
: it's the only place that (i) is guaranteed to be called for evey param and (ii) can access the class.iter_values()
andcast_value()
. I'd want to modify the latter to update all the linked params, in such a way that the cast function is not called again. Maybe these bound getters could have awith_value()
context manager that temporarily overrides the value to use.cleanup()
method to the bound getters that gets called after the value is generated.cleanup()
method, I'd have to refresh all the linked params before yielding the new value, because onceValuesIter
yields, it's not guaranteed to be called again. This leads to a bit of weirdness in that all the linked parameters will get values before the original parameter, but I suppose that's ok. It is possible for parameters to depend on other parameters, so the order in which things are loaded could matter, but practically I don't think it would be a big deal.