omegaconf icon indicating copy to clipboard operation
omegaconf copied to clipboard

Feature: nested string interpolations

Open Jasha10 opened this issue 3 years ago • 7 comments

Currently nested interpolations, e.g. "${b_${c}}", do not seem to be supported.

Here is an example:

from omegaconf import OmegaConf

cfg = OmegaConf.create(
    {
        "a": "${b_${c}}",
        "c": 123,
        "b_123": "xyz",
    }
)
print(cfg.a)

The desired output would be xyz. The current behavior is to throw a GrammarParserError.

Describe alternatives you've considered Here is a workaround that uses getattr to explicitly dereference the interpolation:

cfg = OmegaConf.create(
    {
        "a_ptr": "b_${c}",
        "c": 123,
        "b_123": "xyz",
    }
)
print(getattr(cfg, cfg.a_ptr))

Additional context

>>> omegaconf.__version__
'2.1.0dev20'

Jasha10 avatar Mar 16 '21 18:03 Jasha10

String interpolations does not support nested interpolations right now. You can see examples of supported forms of nested interpolations in the docs. I am not sure how hard it would be to add (@odelalleau, I am pretty sure you thought about it, what do you think?). We should probably either enable it or document that it's not supported at this time.

@Jasha10, do you have an actual use case or are you just pointing it out because it surprised you?

omry avatar Mar 16 '21 18:03 omry

I am not sure how hard it would be to add (@odelalleau, I am pretty sure you thought about it, what do you think?).

IIRC at some point you mentioned that there was no need to support it so it's not something I kept -- pretty sure it was possible in one of my many versions :) (but I may not be remembering correctly, it's been a while -- probably sometime around July/August 2020)

There are some workarounds in case someone really needs it:

# Use an intermediate variable to hold the full name
cfg = OmegaConf.create(
    {
        "a_ref": "b_${c}",
        "a": "${${a_ref}}",
        "c": 123,
        "b_123": "xyz",
    }
)
print(cfg.a)
# Use a resolver
OmegaConf.register_new_resolver("identity", lambda x: x)
cfg = OmegaConf.create(
    {
        "a": "${${identity:b_${c}}}",
        "c": 123,
        "b_123": "xyz",
    }
)
print(cfg.a)

In terms of enabling it in the grammar, it wouldn't be too hard, it just wouldn't play nice with #600 => would need to change a bit the approach taken here (handling $ in the parser instead of the lexer -- it's doable, actually my first version was doing that before I realized it was simpler to do it at the lexer level).

odelalleau avatar Mar 16 '21 18:03 odelalleau

Yes, I do have a use case. I'll try to come up with a minimal practical example...

Edit: for future reference, here is my motivating example:

@dataclass
class Config:
    provider: str  # provider1 or provider2
    service: str  # serviceA or serviceB
    message_format: str = "${message_format_table.${provider}_${service}}"
    message_format_table: Dict[str, str] = ...

Jasha10 avatar Mar 16 '21 18:03 Jasha10

Yeah yeah, blame me. I was probably just trying to cut down on the scope :P.

If anyone wants to try to enable it go ahead but I am learning toward revisiting in 2.2.

omry avatar Mar 16 '21 18:03 omry

There are some workarounds in case someone really needs it:

# Use an intermediate variable to hold the full name
cfg = OmegaConf.create(
    {
        "a_ref": "b_${c}",
        "a": "${${a_ref}}",
        ...

I wasn't aware that this was possible. Thanks for the tip!

Jasha10 avatar Mar 16 '21 18:03 Jasha10

If anyone wants to try to enable it go ahead but I am learning toward revisiting in 2.2.

Not going for it myself but @Jasha10 if you want to take a stab at it let me know, I'll give you some pointers.

odelalleau avatar Mar 16 '21 19:03 odelalleau

Ok, sounds good :)

Edit: just reread the above, I think revisiting in 2.2 sounds better given that there exist viable workarounds.

Jasha10 avatar Mar 16 '21 19:03 Jasha10