6

This is not a duplicate, as this regards using the newer str.format(), the above linked question is also of lower quality, and I think this question is sufficiently different to justify its canonicity.

The question:

One might expect an error here, but we can provide the str.format method with unused keyword arguments.

>>> '{a}{b}'.format(a='foo', b='bar', c='baz')
'foobar'

This enables code like:

>>> foo = 'bar'
>>> baz = 'fizzbuzz'
>>> '{foo}{baz}'.format(**locals())
'barfizzbuzz'
>>> baz = datetime.datetime.now()
>>> '{foo}_{baz}'.format(**locals())
'bar_2013-12-20 18:36:55.624000'

Is this a good practice to do inside a closure like a function?

def make_foo_time_string():
    foo = 'bar'
    baz = datetime.datetime.now()
    return '{foo}{baz}'.format(**locals())

What would be the possible downsides? Extra variables loaded using extra cycles? Is this a matter of convenience? I don't recall seeing this used much, would it be considered idiomatic of Python?

Update I have found a canonical suggested usage using the old style string interpolation: https://wiki.python.org/moin/PythonSpeed/PerformanceTips Nevertheless, it does seem a rather old document.

"Even better, for readability (this has nothing to do with efficiency other than yours as a programmer), use dictionary substitution:"

out = "<html>%(head)s%(prologue)s%(query)s%(tail)s</html>" % locals()
Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
  • There's a new and canonical way in 3.6: formatted string literals: https://docs.python.org/3/whatsnew/3.6.html#whatsnew36-pep498 or https://stackoverflow.com/a/44442301/1144854 – Jacktose Jun 08 '17 at 17:47
  • Is there a downside? I would say so, but only if the message to be formatted could ever be a variable itself. That is to say, if someone can pass you the string to format, they can basically execute arbitrary code in the form of the `__str__` (and `__repr__`?) methods of all local variables. – ThorSummoner Nov 20 '17 at 22:15

2 Answers2

6

What would be the possible downsides? Extra variables loaded using extra cycles? Is this a matter of convenience? I don't recall seeing this used much, would it be considered idiomatic of Python?

You just hit on the biggest downside: It's not idiomatic.

And the reason it's not idiomatic is, as the Zen says, "Explicit is better than implicit."

Obviously many people would not write '{foo}{baz}'.format(foo=foo, baz=baz) (especially if you get too far beyond 2 variables), because that violates DRY pretty horribly… but there's a way to do this that's just as concise as your code, and at least as explicit, and doesn't require sort-of-advanced-level knowledge like locals:

>>> '{}{}'.format(foo, baz)

This doesn't work when the format string is dynamic, but that's a good thing, because dynamic format strings are usually a red flag.

(Some people have also argued the "static-checking" benefit here—if you embed the names in the format string, you get a much less decipherable error than the simple NameError from using a variable that doesn't exist in the arguments to format. I put this in parentheses near the bottom because I personally don't think this argument is very good, even if I have seen it multiple times…)

However, as with any style/idiom question, there is no universal agreement on this one, and it gets debated regularly on python-list (and python-ideas, when someone suggests a way to make locals() a default for format or the like) until everyone stops paying attention.

abarnert
  • 354,177
  • 51
  • 601
  • 671
5

From PEP 20 - The Zen of Python:

Explicit is better than implicit.

Here, that could (and to me, does) mean that it's better to spell out what you want to do. Suppose you eventually want to replace that with a method call and go from

>>> '{a}{b}'.format(a='foo', b='bar', c='baz')

to

>>> mymethod(a='foo', b='bar', c='baz')

Voila - done. But more practically, PyLint and friends can give you valuable information about the explicit version. Suppose you have:

foo = '123'
print '{foo}'.format(**locals())

Great. That works. But what if you forgot to assign to foo? Your code will blow up and you won't know why. Compare with:

print '{foo}'.format(foo=foo)

where PyLint will give you an "Undefined name: 'foo'" error. Yay! Bug found before you've even started to look for it.

In general, be explicit. It's Pythonic, but it's also really good practice.

Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
Kirk Strauser
  • 30,189
  • 5
  • 49
  • 65
  • You're sticking the strings `'foo'`, `'bar'`, and `'baz'` into the format string instead of the local variables `foo`, `bar`, and `baz` as in the original post. The most Pythonic way to do what you're doing is probably just `'foobar'` without using `format`… – abarnert Dec 20 '13 at 23:56
  • I like debugging using `print("foo is {foo} bar is {bar} baz is {baz}".format(foo=foo,bar=bar,baz=baz))` is great to track down the errant variable ("Oh oops, no wonder my loop is terminating early, I used i**=2 instead of i*=2") and what have you. print(foo,bar,baz) is not as helpful for that. – Adam Smith Dec 20 '13 at 23:59
  • 1
    @adsmith: Except of course when you type `print("foo is {foo} bar is {baz}"`… and don't notice the typo in your quickly-written debugging statement. Not that I've ever wasted 6 hours of a day on a stupid bug like that. :) – abarnert Dec 21 '13 at 00:01
  • @abarnert I was going for the simplest possible example, just for the sake of getting the idea across. Yeah, that'd be a very heavyweight way to write that string. Don't do that. :-D – Kirk Strauser Dec 21 '13 at 00:01