Flynt: Convert old Python string formatting to f-strings
github.com
github.com
I guess you need the `--py36-plus` CLI option to reproduce the desired behaviour https://github.com/asottile/pyupgrade#f-strings
thought that might result in lots of other edits to the source, this in particular seems unwarranted "foo".encode("utf-8") --> "foo".encode() because the default encoding in py3+ is utf-8. What happened to explicit is better than implicit?
AFAIK the % operator is not deprecated. In fact % formatting being syntactically near identical to C is a feature to me. I use format strings as well depending on context.
% formatting being older doesn't mean it's obsolete.
That said it is cool to design a script to transform your code and any time you spent in the developing of automation is time earned.
I can see an in-house style guide strongly recommending f-strings over % formatting
In [4]: a, b, c, d = ("123", "456", "789", "0ab")
In [5]: %timeit a + "-" + b
139 ns ± 10 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [6]: %timeit "%s-%s" % (a, b)
198 ns ± 13.8 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [7]: %timeit "{}-{}".format(a, b)
248 ns ± 9.17 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [8]: %timeit f"{a}-{b}"
99 ns ± 2.48 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [9]: %timeit a + "-" + b + "-" + c + "-" + d
344 ns ± 10.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [11]: %timeit "%s-%s-%s-%s" % (a, b, c, d)
290 ns ± 9.64 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [12]: %timeit ''.join([a, '-', b, '-', c, '-', d])
245 ns ± 2.32 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [13]: %timeit "{0}-{1}-{2}-{3}".format(a, b, c, d)
471 ns ± 23.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [14]: %timeit f"{a}-{b}-{c}-{d}"
176 ns ± 11.9 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)Hmm. I have a long habit of (almost) always using a one-element tuple. Consider:
"foo %s" % x
The % operator on strings has a special case for tuples: >>> x = "bar"
>>> "foo %s" % x
'foo bar'
>>> x = ("bar",)
>>> "foo %s" % x
'foo bar'
If you really want str(x) and are unsure that x might be a tuple, then you must use the tuple form: >>> "foo %s" % (x,)
"foo ('bar',)"
or for a more involved example: >>> from collections import namedtuple
>>> x=namedtuple("Foo", "foo")("bar")
>>> "foo %s" % x
'foo bar'
>>> "foo %s" % (x,)
"foo Foo(foo='bar')"
This might occur with input validation, like: if isinstance(x, int):
return f_int(x)
elif isinstance(x, float):
return f_float(x)
else:
raise ValueError("Unsupported value: %s" % (x,))
In my code base there are at least 347 occurrences of this form: % cat *.py */*.py | egrep -c '% \([a-zA-Z0-9_]+,\)'
347In general I don’t understand the need to hard-standardize code. Making subtle decisions depending on context is an important part of programming, please let humans decide and don’t use these types of tools in commit hooks...
Yes, and so is deciding what context matters across a whole team or team-of-teams.
Whenever I see this word, I see a trust or communication problem.
your_dict = {
"some": "awesome",
"from": "out of",
"dictionary": "mapping",
}
"insert {some} values {from} a {dictionary}".format(**your_dict) your_dict = {
"some": "awesome",
"from": "out of",
"dictionary": "mapping",
}
"insert %(some)s values %(from)s a %(dictionary)s" % your_dict
WRT .format, '%' is safer to use with untrusted (well, semi-trusted) format strings, whereas .format can lead to code injection (in more insidious ways than with f-strings).It was a fun experience :)
It might be a little bit easier to writer, but now you have magic and also code that is mixed within what should be simple strings. It is so easy to have suprising bad side effects because some operation would have been hidden in strings.
r"" strings for raw representation (this breaks most syntax highlighters though)
u"" for unicode string
b"" for bytes string.
f"" would totally fit as a "formatted string".
(R&B strings were available in python2, for context).
Personally I find the use case between simple %-interpolation of dumb strings and full blown templates pretty small.
I liked the ability to put semi-complex anonymous expressions in format and reference them with {}. If you did this it was still be obvious what was going on without having to create variable names like comma_separated_list_of_emails.
Technically this is possible in f strings but it leads to an escaping mess. You wouldn't want to put a comprehension with a ','.join in there, for example.
Even when you did use variable names (e.g. "{addr_list}...".format(addr_list=...") you could safely use shorter names because the scope was tighter.
The same could be said about virtually any feature of any language.
Why would you want to impose artificial constraints on yourself? I really dislike the argument “people will hurt themselves, let’s go back to the old way“