-
Notifications
You must be signed in to change notification settings - Fork 116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OmegaConf.select is relative to the node it's called on #660
Conversation
I think behavior for select should be consistent with interpolations, that is, I like the behavior from #656. |
One alternative (for both select and for interpolations) could be to emulate the convention used by filesystems, where all paths are relative to Either way, my opinion is that |
Changing from absolute by default to relative by default is a huge breaking change that will break all interpolations out there.
from omegaconf import OmegaConf
cfg = OmegaConf.create(
{
"a": {
"b": {
"c": 10,
}
},
"z": 10,
},
)
assert OmegaConf.select(cfg.a, "a") == {"a": {"b": {"c": 10}}}
assert OmegaConf.select(cfg.a, ".b") == {"c": 10} Another option is to allow select to operate in both modes by another flag. |
Ok.
Yes, exactly. My motivation for the proposal was the example you gave:
I thought this was confusing, because
Could work. |
Ok, here is one more idea: Have Edit: |
a33298d
to
470c7e3
Compare
Okay, the ambiguity cannot be resolved based on the key alone: cfg = OmegaConf.create({"a": {"b" : 10}, "b": 20})
OmegaConf.select(cfg.a, "b") # which b is it? I added a parameter to select: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't read all discussions, but if I understand correctly, the idea would be to set absolute_key
to True for a future oc.select
resolver? Although I'm ok with it, by looking at these examples I still find the expected behavior potentially a bit confusing.
This is actually related to this excerpt from the example in the original PR description:
b: ${oc.select: .a) # relative to foo, one level up: 10
If I'm not mistaken, this specific line is actually incorrect, because OmegaConf.select(cfg.foo, ".a")
would actually return cfg.foo.a
(= 20).
But personally, I would find more intuitive for OmegaConf.select(cfg.foo, ".a")
to indeed go one level up above foo
then select a
, i.e. return cfg.a
.
In other words, it would make sense for me for OmegaConf.select()
to work this way for relative paths:
- Go up the parents hierarchy as many times as there are dots (note: this means that the input node doesn't need to be a container)
- Then go down the rest of the path
I also believe that, if a resolver oc.select
is added with such a signature, then b: ${oc.select: .a)
should resolve to 20 in the example I quoted above. In that case, select()
would actually be called on the node b
(not its parent).
That being said, I'm not entirely sure what would be the purpose of oc.select
(what does it allow that you can't do with interpolations?), and if we add one, wouldn't it make more sense for it to mimic the OmegaConf.select()
signature, i.e., take a node as first input?
# 2. relative to the config root | ||
# This is controlled by the absolute_key flag. By default, such keys are relative to cfg. | ||
if not absolute_key and not key.startswith("."): | ||
key = f".{key}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The following achieves the same result but is a bit more efficient:
if absolute_key or key.startswith("."):
cfg, key = cfg._resolve_key_and_root(key)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is it more efficient? (I am expecting absolute_key to be False in most cases).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the current code we will often go through this if
condition (with absolute_key
set to False and key
absolute), which triggers the following steps:
- updating the
key
string by pre-pending a dot - calling
_resolve_key_and_root()
, which will remove the dot we just added
My suggestion gets rid of both of these. But it probably doesn't matter much given everything else that happens later in most situations, so it's really not a big deal.
Yes, Does that make sense? foo:
a: 1
b: 2
thing:
bar:
a: 3
b: 4
base1: ${foo} # {a:1, b:2}
# all are [1,2]
k1: ${oc.dict:values: ${foo}} # absolute interpolation
k2: ${oc.dict:values: foo} # absolute selection
k3: ${oc.dict:values: ${..foo}} # relative interpolation (one level up)
k4: ${oc.dict:values: ..foo} # relative selection (one level up)
base2: ${bar} # {a:3, b:4}
# [3,4]
k5: ${oc.dict:values: ${.bar}} # relative interpolation (same level)
k6: ${oc.dict:values: .bar} # relative selection (same level)
If I am not mistaken, that would have been the behavior before this fix. it's not a desired behavior.
Would you also find it intuitive for
Yes, I this is the desired behavior.
It allows for a selection with a default value.
Another use case I have in mind is an shiny_new_field: 10
rusy_old_field: ${oc.deprecated, shiny_new_field) should be equivalent to shiny_new_field: 10
rusy_old_field: ${shiny_new_field) But will also issue a warning like:
|
Yes, I agree that for custom resolvers taking both nodes/strings, we want the same syntax to yield the same results.
In the current master,
No.
In most situations users should be able to simply use:
Aaah right, now I remember.
Sounds good. Summary of my thoughts so far:
|
To me the current behavior is like that of accessing files in the file system.
For a file system, ./a and a are equivalent and are both relative to cwd. The reason I added support to relative interpolations to OmegaConf.select was to enable custom resolvers to use it. We could potentially introduce a second API to select using interpolation style keys and remove the relative key support from OmegaConf.select() (with a proper error telling the user to use the other method if they try to use relative keys). As for the name of OmegaConf.dereference(cfg, interpolation_key) and: a: 10
b:
a: 20
# this is what oc.select would do when called here, if we supported it too:
c: ${oc.select: a} # 20
c: ${oc.select: .a} # 10 or error if we don't want to support relative keys in select.
d: ${oc.dereference: a} # 10
d: ${oc.dereference: .a} # 20 Another option is oc.readlink, which is similar to the system call to resolve symbolic links. (but I guess that would return the final path of the interpolation and not the actual value). |
Another option is |
nope, we already have OmegaConf.resolve and it will be confusing :). |
I had actually thought about the file system analogy but didn't find it convincing, since in general going up in the file system is done by increment of However, more importantly (to me), when I see an interpolation like
Just to be sure I understand, this behavior of Regarding the name, I'm still unsure. I like |
Going up more than one level in the file system requiring ../ is not really the point.
(The behavior for links in b is the same if you are inside b).
The Interpretation that works (here and in symlinks), is: a: 10
b:
a: 20
# this is what oc.select would do when called here, if we supported it too:
c: ${oc.select: a} # 20
c: ${oc.select: .a} # 10 or error if we don't want to support relative keys in select.
d: ${oc.dereference: a} # 10
d: ${oc.dereference: .a} # 20
Yes, my idea for oc.select was that it would select the same value an node interpolation would. (similarly, any custom resolver that operates on string keys should probably operate in that way too). |
Alright, let's roll with it, I don't want us to spend too much time discussing what is or is not intuitive for me personally :) |
The point I was making is that this is consistent with how relative file system symlinks are behaving, which are pretty common. About the name of oc.select: the jury is still out. |
Yes for interpolations -- it's with |
Gotcha.
|
Here are the analogous omegaconf objects (using "oc.select" for now, even if the name may change): cfg1 = OmegaConf.create(
{
"a": "top level a",
"b": {
"a": "nested a",
"z1": "${oc.select: a}", # nested a
"z2": "${oc.select: .a}", # nested a
"z3": "${oc.select: ..a}", # top level a
},
}
)
cfg2 = OmegaConf.create(
{
"a": "top level a",
"b": {
"a": "nested a",
# "z1": ...
"z2": "${.a}", # nested a
"z3": "${..a}", # top level a
},
}
) I find it very compelling that |
The support added for relative keys in select (#656) was a breaking change.
in 2.0, when select is called on a nested node, it's already relative.
#656 made that select absolute, which is a breaking change.
A non breaking fix is to keep nested selects relative, but add support for relative syntax (specifically for going up the hierarchy).
The resulting behavior is shown below and can be a bit surprising:
In most scenarios, going up a level requires two dots. but since OmegaConf.select on a nested node is already relative to that node, only one dot is needed.
The API is establishing the non-breaking implementation.
An alternative is to make this breaking (the behavior from #656).
The non-breaking behavior will look weird when using select inside custom resolvers using select as an alternative way to access nodes (this is using a planned oc.select resolver that will just call OmegaConf.select):
I am not particularly happy with this.
On the one hand, select being relative to the node it's called on is intuitive and is the current behavior.
On the other hand, it's inconsistent with interpolations.
Thoughts?
I am planning to merge this because this is fixing an unintentional breaking change, but let's decide if we actually want to make this an intentional breaking change (and at the point, users will need to use relative select syntax to get relative behavior, like in interpolations.
EDIT:
Final solution is different than anything above.
read the code and look at the tests to understand it.