Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trigger f-string parse on python string in variable

This question comes from handling jupyter magics, but can be expressed in a more simple way. Given a string s = "the key is {d['key']}" and a dictionary d = {'key': 'val'}, we want to parse the string.

The old method would be .format(), which will raise an error - it doesn't handle dictionary keys.

"the key is {d['key']}".format(d=d)  # ERROR

I thought the only way around was to transform the dictionary to an object (explained here or here).

"the key is {d.key}".format(obj(d))

But Martijn explained nicely that you can simply leave out the quotes to get it working:

"the key is {d[key]}".format(d=d)

Still the new method f'string' does handle dictionary keys ain an intuitive python manner:

f"the key is {d['key']}"

It also handles functions - something .format also cannot handle.

f"this means {d['key'].lower()}"

Although we now know that you can do it with .format, I am still wondering about the original question: given s and d, how do you force a f'string' parse of s? I added another example with a function inside the curly brackets, that .format can also not handle and f'string' would be able to solve.

Is there some function .fstring() or method available? What does Python use internally?

like image 921
Roelant Avatar asked Sep 03 '26 03:09

Roelant


2 Answers

String formatting can handle most string dictionary keys just fine, but you need to remove the quotes:

"the key is {d[key]}".format(d=d)

Demo:

>>> d = {'key': 'val'}
>>> "the key is {d[key]}".format(d=d)
'the key is val'

str.format() syntax isn't quite the same thing as Python expression syntax (which is what f-strings mostly support).

From the Format String Syntax documentation:

field_name        ::=  arg_name ("." attribute_name | "[" element_index "]")*
[...]
element_index     ::=  digit+ | index_string
index_string      ::=  <any source character except "]"> +

and

[A]n expression of the form '[index]' does an index lookup using __getitem__()

The syntax is limited, in that it will convert any digit-only strings into an integer, and everything else is always interpreted as a string (though you could use nested {} placeholders to dynamically interpolate a key value from another variable).

If you must support arbitrary expressions, the same way that f-strings do and you do not take template strings from untrusted sources (this part is important), then you could parse out the field name components and then use the eval() function to evaluate the values before you then output the final string:

from string import Formatter

_conversions = {'a': ascii, 'r': repr, 's': str}

def evaluate_template_expressions(template, globals_=None):
    if globals_ is None:
        globals_ = globals()
    result = []
    parts = Formatter().parse(template)
    for literal_text, field_name, format_spec, conversion in parts:
        if literal_text:
            result.append(literal_text)
        if not field_name:
            continue
        value = eval(field_name, globals_)
        if conversion:
            value = _conversions[conversion](value)
        if format_spec:
            value = format(value, format_spec)
        result.append(value)
    return ''.join(result)

Now the quotes are accepted:

>>> s = "the key is {d['key']}"
>>> d = {'key': 'val'}
>>> evaluate_template_expressions(s)
'the key is val'

Essentially, you can do the same with eval(f'f{s!r}', globals()), but the above might give you some more control over what expressions you might want to support.

like image 89
Martijn Pieters Avatar answered Sep 04 '26 16:09

Martijn Pieters


[G]iven s and d, how do you force a f'string' parse of s? Is there some function or method available?

This can be done... using eval. But beware eval!

>>> eval('f' + repr(s))
the key is val

The repr is there to escape any quotes and to wrap s itself with quotes.

If you are aware of which variables to format (d in this case), opt for Martijn's answer of doing str.format. The above solution should be your last resort due to the dangers of eval.

like image 29
TrebledJ Avatar answered Sep 04 '26 17:09

TrebledJ



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!