56

I was reading this blog post on Python's new f-strings and they seem really neat. However, I want to be able to load an f-string from a string or file.

I can't seem to find any string method or other function that does this.

From the example in the blog post:

name = 'Fred'
age = 42
f"My name is {name} and I am {age} years old"

'My name is Fred and I am 42 years old'

But what if I had a string s? I want to be able to eff-ify s, something like this:

name = 'Fred'
age = 42
s = "My name is {name} and I am {age} years old"
effify(s)

It turns out I can already perform something similar to str.format and garner the performance pick up. Namely:

format = lambda name, age: f"My name is {name} and I am {age} years old"
format('Ted', 12)

'My name is Ted and I am 12 years old'
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
piRSquared
  • 285,575
  • 57
  • 475
  • 624
  • 15
    `s.format(name="John", age=10)` or `s.format(**globals)` or similar. – turbulencetoo Nov 16 '17 at 21:13
  • f-strings are new in python 3.6 and are literals not callables. I know how to get something that works similarly. What I want is to make an existing string (possibly loaded from a file) and have it be a formatted string literal. – piRSquared Nov 16 '17 at 21:17
  • So if I understand you correctly, your asking how you can control when the expressions in f-strings are evaluated, @piRSquared? – Christian Dean Nov 16 '17 at 21:19
  • 1
    Have yourself a [nice read](https://www.python.org/dev/peps/pep-0498/#id48) :) – zipa Nov 16 '17 at 21:21
  • IIUC, Not, unfortunately, unless you use `exec` :( (at least, I think so) – cs95 Nov 16 '17 at 21:21
  • @turbulencetoo Best solution imo, Unfortunately, the syntaxes differ slightly: eg `f"{ d['a'] }"` has to be rewritten as `{d[a]}` (spaces not allowed and dict keys have no quotes. Also more complex expressions are not allowed) – Dill Aug 26 '18 at 09:22
  • Related: [Transform string to f-string](https://stackoverflow.com/q/44757222/4518341) – wjandrea Jul 18 '20 at 19:15
  • Beside the point, but [named lambdas are bad practice](https://stackoverflow.com/a/38381663/4518341), and `format` is a bad variable name since it shadows the builtin `format`. I would do what @turbulencetoo recommended instead. – wjandrea Jul 18 '20 at 19:17
  • `str.format` is literally a better approach than f strings. f strings should be depreciated and removed in future versions. – NeilG May 11 '23 at 01:13

3 Answers3

35

f-strings are code. Not just in the safe, "of course a string literal is code" way, but in the dangerous, arbitrary-code-execution way. This is a valid f-string:

f"{__import__('os').system('install ransomware or something')}"

and it will execute arbitrary shell commands when evaluated.

You're asking how to take a string loaded from a text file and evaluate it as code, and the answer boils down to eval. This is of course a security risk and probably a bad idea, so I recommend not trying to load f-strings from files.

If you want to load the f-string f"My name is {name} and I am {age} years old" from a file, then actually put

f"My name is {name} and I am {age} years old"

in the file, f and quotation marks and all.

Read it from the file, compile it and save it (so eval doesn't have to recompile it every time):

compiled_fstring = compile(fstring_from_file, '<fstring_from_file>', 'eval')

and evaluate it with eval:

formatted_output = eval(compiled_fstring)

If you do this, be very careful about the sources you load your f-strings from.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • 19
    I don't quite understand why anybody would use compile and eval instead of `string_from_file.format(**locals())`. If the answer is "because f-strings can have more complex expressions", writing a small DSL that handles all required expressions seems preferable to using eval and allowing arbitrary code execution. – l4mpi Aug 27 '18 at 13:17
  • @l4mpi could you provide an example? I am facing this issue and would like to try it your way (my fallback is a templating engine like Jinja) – stevepastelan Oct 07 '20 at 02:17
  • 7
    @stevepastelan basically I'm recommending the `str.format` method instead of f-strings. For an f-string like in this answer, `myStr=f"my name is {name} and I'm {age} years old"`, you just drop the leading `f` and use `myString.format(name=, age=)` to get the formatted output. If you want to get the placeholder values from the scope instead of passing them individually, use `myStr.format(**locals())` to pass all local vars to `format`. Of course that doesn't work for expressions in f-strings (e.g. `{age+1}`), so you'd have to handle those yourself if you need them... – l4mpi Oct 13 '20 at 13:58
  • @stevepastelan expressions are where DSLs etc come into play. E.g. if you need to support addition and a ternary if-operator, you could create a fitting syntax for your format string, parse the string accordingly (either by hand or with e.g. ANTLR), and write a small class that handles evaluating the expressions and creating the output. Of course if you need to support complex expressions you might be better off using an existing templating engine, or just using f-strings, but personally for production code I'd need very compelling reasons to use anything that allows arbitrary code execution. – l4mpi Oct 13 '20 at 14:06
21

A simple solution would be to use f-strings and eval.

def effify(non_f_str: str):
    return eval(f'f"""{non_f_str}"""')

name = 'Fred'
age = 42
s = "My name is {name} and I am {age} years old"
effify(s)
'My name is Fred and I am 42 years old'

This basically prepends an "f" to the string and then evaluates as code. The triple quotes help to accommodate multiline strings as well. The function will try to pull the variables referenced in the f-string from the scope around its call. As pointed out, using eval can potentially be dangerous, but if you know your source, then I think it is not more dangerous than executing any other code.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jarno
  • 6,243
  • 3
  • 42
  • 57
10

But what if I had a string s? I want to be able to eff-ify s, something like this:

name = 'Fred'
age = 42
s = "My name is {name} and I am {age} years old"
effify(s)

AFAIU, According to PEP 498 -- Literal String Interpolation, this isn't possible1. There's no way to programmatically create an f-string:

In Python source code, an f-string is a literal string, prefixed with 'f', which contains expressions inside braces.


1Unless of course, you'd be willing to use something like exec as @coldspeed mentioned. But at that point, the cons probably outweigh the pros.

Community
  • 1
  • 1
Christian Dean
  • 22,138
  • 7
  • 54
  • 87
  • 1
    I think he's looking for some function `foo` such that `foo("{x}")` returns `f"{x}"` – Patrick Haugh Nov 16 '17 at 21:24
  • @PatrickHaugh Ah, I see what you mean. Couldn't quite figure out what he was asking. I'll delete my answer. – Christian Dean Nov 16 '17 at 21:25
  • I'll take impossible for now. If this changes in future, I'll update this post. – piRSquared Nov 16 '17 at 21:54
  • @ChristianDean I unaccepted because I'm anticipating accepting the other answer once I get it to work. Thank you again for the answer. – piRSquared Dec 01 '17 at 18:01
  • @piRSquared hey man, no problem. I appreciate you letting me know. If you do get it to work, maybe you can post back here with an answer. I'm sure others would be interested, including myself : ) – Christian Dean Dec 01 '17 at 19:54