Don't realize that this is exactly what f-strings are doing? If you accept the `f""` version but not the `"".format_map(locals())`, you're lying to yourself[1]. At least the `str.format_map(locals())` isn't magic (and IMO, much more pythonic (by virtue of explicit), although not as pythonic as `str.format(template_var=scoped_var)`).
The only way `str.format_map(locals())` is a code-smell is if the people working on the code don't actually know Python. Builtin methods of core types, dictionary unpacking, and builtin functions are all extremely common and only complete novices at python would find any issue with them. Compare that to what you need to know when you encounter `f"hello {name}"`:
0) am I working with Python 3.6+?
1) when does replacement happen?
2) what scopes are examined for substitution?
Of course, 2) is a trick question because you EVALUATE ARBITRARY PYTHON EXPRESSIONS IN THE FORMAT STRING![2]
[1] Technically, PEP-0498 states that we're not exposing a full locals() or globals(), but this actually matters very little. The danger here is that both .format() and f"" use object.__format__() (falling back __repr__ IIRC) to figure out the replacement. If your object.__format__() is malicious, it gets access to more data than it needs. However, since python has zero security model, if it's malicious, it pings whatever command and control server it's reporting to and starts up a thread listening for commands anyway (aka you're fucked). Realistically, this means that, you might leak credentials (or other sensitive information) to third party loggers, but only if your method for censoring outgoing log/exception data is a a poorly-built blacklist.
Yeah, I don't really have a problem with the old way:
print("My robot's name is {}. It's {} years old.".format(name, years_old))
Seems reasonable to me. I don't really see any reason to hack in the locals splat. I guess the one thing it saves you is ordering issues, but in practice, I haven't found those to be terribly inconvenient, and in the rare case they are, you can just do "{name}, {age}".format(name=name, age=years_old)).
Yes, both forms are a little longer, but as you state, it's easier to see what's going on. One of Python's biggest attractions, IMO, is that it's batteries included, but not batteries-sealed-under-1000-layers-of-carbonite-locked-by-a-mystical-incantation, like many other dynamic languages. It's usually pretty easy to get in there and see what's happening without having to traverse tons of indirection due to Python's design principle favoring explicit programming styles.
I won't lie and say I've never wished for more conventional style string interpolation, but it certainly wasn't a showstopper, and it's not something we should assume we get for free.
I was just using the locals() to show equivalence. I think I've done that maybe a handful of times in production code, and it was always an already hairy templating situation. Most of the time, I see:
"{} world!".format('hello')
or
"{greeting} world!".format(greeting='hello')
There is a huge value in Python's historical approach of straightforward code built on a relatively small foundation. f-strings are another example of the Python I fell in love with growing up/moving on.
Asterisks only show in code blocks. They denote italics in HN's markup. Double asterisks outside of a code block begin and end empty italicized text.
** no italics because we're in a code block
> Second, the parser at compile time pulls out the actual expressions and fills them in to a .format call. It __does not__ use locals!
I'll quote myself here, since you missed the first part of this sentence when jumping to a conclusion about why security is irrelevant, but we need to blacklist a well-understood and widely used builtin function anyway.
>> Technically, PEP-0498 states that we're not exposing a full locals() or globals(), but this actually matters very little.
But, while we're trying to be pedantic, let's actually be pedantic:
If you'd like, you can go ahead and examine what it is that FORMAT_VALUE does (hint: invokes PyObject_Format which is defined in abstract.c around line 670 in the Python-3.6.0 tarball).
The guy that implemented it (Eric) said that it is largely syntactic-sugar where the byte compiler splits the string and converts it into a format(string, args) call. Therefore it gets exactly/only what it needs.
It is true that I've never inspected the C code implementation myself, but I took him at his word.
load_name looks like it read foo, then format_value did format('{}', foo).
I didn't realize it before, but it looks like it might have better performance.
That was the original implementation, AFAICT from reading the ticketing. FORMAT_VALUE was introduced to fix a slight inconsistency where someone might monkey with str.format(). While doing so, it was mentioned that there was a slight performance improvement to fewer lookups. See here for more info: https://bugs.python.org/issue25483
Well that's a definite code smell.