Yes, I know you can do more with format strings than plain interpolation, but that's all that the basic examples show.
(Also: Perl has allowed underscores in number literals for ages).
Python just got one more way of doing string interpolation.
This is a textbook example of string interpolation.
You can look it up on wikipedia. I am not sure what you mean.
The string "My name is {name} and I am {age} years old" is a plain string. By itself, there is no interpolation:
>>> x = "My name is {name} and I am {age} years old"
>>> x
'My name is {name} and I am {age} years old'
The format function is what confers meaning. This is identical to a standard printf situation, wherein the string "%d %x" encodes a transformation but in and of itself isn't a transformation.Contrast with the proposal for f-strings:
>>> x = f"My name is {name} and I am {age} years old"
# this is an error unless name and age are defined
The literal mandates a particular interpretation, and the f-string cannot exist outside of that in the same way that the standard string does. Or as the PEP puts it:> It should be noted that an f-string is really an expression evaluated at run time, not a constant value.
But when you do: "{foo} {bar}" % {"foo": "blah", "bar": "blooh"}, this is string interpolation.
Is there a definition, anywhere(even a book would suffice), where it has your strict definition of what interpolation is?
I'm not trying to undermine the usefulness of the interpolated expressions and what you said. I just really want to know if the definition is so strict.
UPDATE: http://rosettacode.org/wiki/String_interpolation_(included)
I think that you are the one who's making the definition so specific and strict. I can't find a single source on the Internet that agrees with your definition.
string interpolation is the process of evaluating a string literal containing
one or more placeholders, yielding a result in which the placeholders are
replaced with their corresponding values.
I read this as saying that the evaluation of the string literal yields a string where the substitution has already been made. That's not the case with the format style of "string interpolation." You provide a template string and call a function with some arguments, and that replaces the placeholders. The string literal does not handle the substitutions itself. I believe the Wikipedia article agrees that this is the deciding factor.A few sentences later, the article includes this statement:
Some languages do not offer string interpolation, instead offering a standard
function where one parameter is the printf format string, and other(s) provide
the values for each placeholder.
This sounds pretty similar to what was in Python prior to this release (especially if you know how methods work in Python).Also notice how the statement begins by saying that not all languages offer string interpolation. I think that's the final nail in the coffin of the argument. You could use virtually any language to write a function that handles substitutions in template strings. If that qualified as string interpolation then every language would have it. Since some languages do not, I think we can safely say that we're talking about a syntax feature.
See: list comprehensions obsoleting most uses of map() and filter(), then generator expressions obsoleting any uses of list comprehensions that don't need a list object
Here's four ways to do the same thing.
Naïve way:
foo = []
for x in bar:
if meets_some_condition(x):
foo.append(transform(x))
do_something_with(foo)
With map/filter: do_something_with(map(transform, filter(meets_some_condition, bar)))
With list comprehensions: do_something_with([transform(x) for x in bar if meets_some_condition(x)])
With generator expressions: do_something_with(transform(x) for x in bar if meets_some_condition(x))
All four of those are valid in modern Python, but the latter and only the latter is the right way to do it.Before Python 2.0, either map/filter or the naïve way was the right way (TBH, I'm not familiar with what was considered right in the old days). When Python 2.0 came out, list comprehensions became the right way. When Python 2.4 came out, generator expressions became the right way. The right way depends on your version of Python.
Or for that matter, look at old-style classes vs. new-style classes. When new-style classes came out, they immediately became the one and only right way to do classes, but old style classes still stuck around for the rest of the 2.x series.
An implicit coding style that requires the reader to look up the semantics of Python's comprehensions (and also makes it harder to unwind/inject debugging/keep line lengths reasonable) is a negative unless you're gaining something besides brevity.
I understand some claim that comprehensions are faster for various reasons, but this is presumably implementation dependent and shouldn't be assumed unless you know it's the case in your implementation. I also understand that the generator expression uses less memory, but the generator version can be written the long way too:
def test_bar(bar):
for foo in bar:
if meets_some_condition(foo):
yield transform(foo)
do_something_with(test_bar(bar))
I know that Python is pretty reasonable but I don't like starting the descent down into the alphabet-soup madness that we get in a lot of other languages.But, I agree with you regarding creeping complexity in general - @wrappers/decorators confused me for the longest time, and I often wonder how someone not familiar with the language is supposed to understand what they are doing.
I disagree. For the (common) circumstances where genexp's are appropriate, they are also the most clear and explicit way of expressing intent (explicit loops express the mechanism, not the intent.)
For cases where you need a list object, list comprehension are the most direct expression of intent.
There's cases where explicit looping is the most clear expression of intent; usually, this would be where complex logic is needed within each loop iteration.
--- "Avoid programming languages starting with the letter P except Prolog"
"My name is {name} and I am {age} years old".format(**locals())
... but I always felt a little guilty like this is some kind of dynamic-programming-on-steroids trick that I should use sparingly.Though I think it would be cool if you could have a deferred format string, like so:
template = 'My name is {name} and I am {age} years old. Next year I will be {age + 1}!'
name = 'Amy'
age = 32
formatted = template.apply()
# formatted = 'My name is Amy and I am 32 years old. Next year I will be 33!'
So that way you could store the template as a constant somewhere and apply it after receiving input from the source of your choice.You can kind of do that with [0], but it doesn't evaluate expressions (so the age + 1 thing won't work).
[0] .format(**locals()) # putting it here because HN eats the asterisks if it's not indented def template(name, age):
return f"My name is {name} and I am {age} .."
formatted = template('Amy', 32)
?[Edit: I only started learning Python recently, and this is how I'd think of doing it come from JS:
const template = (name, age) => `My name is ${name} and I am ${age}`;
const formatted = template('Amy', 32);
](Conflict between improvement and backward compatibility, but I miss it.)
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
It pretty clear that if you support python 3.6 you should use `f` strings.And if you support any earlier version of python, you cannot use f strings. Hence, there is a conflict between improvement and backward compatibility.
Python is no longer inferior to Bash and Perl for short scripts and more concise. Looking across the modern language landscape, this functionality is becoming an industry standard. The format string is also a little more sophisticated that it looks on the surface.
Still, I was a bit down that so many showed up to complain about it, mostly with purity or adversity-builds-character arguments. I'm glad lots of people are enjoying it.
For the example above, this might be fine, but most examples that I see using this kind of thing are things like:
"select * from foo where id = '%s'" or "<input type='hidden' name='foo' value='%s'/>" or "rm -rf /path/to/customer/%s"
I tend to think that in general appending strings is more often than not quite a dangerous operation, and it behooves the caller to need to have a long think about exactly how the strings being substituted in need to be escaped - as it's pretty rare that they don't need to be.
I think you just have to play it by ear. Old formatting styles seem like one of those things you want to leave alone for a really long time, because they're so common that it'd be a major PITA to fix all occurrences, and because it doesn't seem like there's an obvious negative ramification to allowing the old styles to continue to evaluate according to the old methods.
I mean, there’s a precedent for short built-in functions like len(), and apparently 'print "x"' as a statement was terrible so 'print("x")' replaced it. If they wanted such consistency, shouldn’t they have used 'f("{a} {b}")' or something? Or alternately, allow p-strings like 'p"hello, world"' to print?
Other cases such as redirecting to a file, and changing the line ending are handled much more intuitively with the print function.
In fact, I don't think it would be possible to write such a function in the general case, because it's ambiguous what environment you would need to use.
For example:
def call(func, arg):
a = 2
return func(arg)
a = 1print(call(f, "{a}"))
Does this print 1 or 2?
One reason might be that f-strings work in expressions, while statements do not. Even with the BDFLs apparent distaste for functional programming constructs, syntax that works within expressions has lots of practical advantages over statements.
>>> 1 + '1'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
No. >>> float('inf') < 'hi'
True
Fixed in Python 3, but still. There are more of these things.I mean that it feels so much nicer to do:
let x = [ 1, 2, 3].map(i => i * 10)
than x = list(map((lambda i: i * 10), [ 1, 2, 3 ])) x = [i * 10 for i in [1, 2, 3]] i. let x = [ 1, 2, 3].map(i => i * 10)
ii. x = list(map((lambda i: i * 10), [ 1, 2, 3 ]))
iii. x = [i * 10 for i in [1, 2, 3]]
All these methods are really just multiple sides of the same coin. All I see is different syntax, none looks any better from my point of view and unless there is a clear performance benefit, just use what you are used to, or what your coding standards define. for i in [1,2,3]:
...It also works for multiple arguments: `_ * _` is equivalent to `(x, y) => x * y`, which is really `(x, y) => x.*(y)`.
In typical perl^Wpython spirit, we now can save a few characters by typing:
f"My name is {name} and I am {age} years old."
instead of: "My name is {name} and I am {age} years old.".format(**locals())
Look how much typing we saved! Now we just have to explain to new users that their strings get eval'd for stuff in brackets, but only when the string is prefixed by `f`...Well that's a definite code smell.
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.
[2] https://www.python.org/dev/peps/pep-0498/#supporting-full-py...
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.
"{} 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.Second, the parser at compile time pulls out the actual expressions and fills them in to a .format call. It _does not_ use locals!
... and your worries about security are not useful, you could make the same statements about an entire code base.
I meant what I wrote. Someone, in a rush to criticize about a topic on which they are ignorant, didn't consult the Python documentation:
https://docs.python.org/3/library/stdtypes.html#str.format_m...
> they don't show here for some reason.
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:
>>> import dis
>>> dis.dis('f"{foo}"')
1 0 LOAD_NAME 0 (foo)
2 FORMAT_VALUE 0
4 RETURN_VALUE
Shouldn't I be seeing some LOAD_CONST, LOAD_ATTR, and CALL_FUNCTION somewhere? >>> import dis
>>> dis.dis('"{foo}".format("bar")')
1 0 LOAD_CONST 0 ('{foo}')
2 LOAD_ATTR 0 (format)
4 LOAD_CONST 1 ('bar')
6 CALL_FUNCTION 1
8 RETURN_VALUE
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).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.