I think a better approach would be to just add special formatter operators (if they aren't already there) that would just call str() or repr() or ascii() to whatever is presented to them (and maybe take some optional arguments such as length or padding).
As well it should. Programming language features should be orthogonal as much as possible.
> It's too complicated and lets user have two different ways of doing things (not Pythonic) - one to calculate expression inside string and the other to calculate it outside (which should IMHO be preferred).
You must hate expression nesting, then. Look at all these ways of doing the same thing:
x = a + b * (c - d)
e = c - d
x = a + b * e
f = b * (c - d)
x = a + f
e = c - d
f = b * e
x = a + f
Clearly, expressions should be restricted to no more than one binary operator. That reduces the number of different ways of computing the expression, and forces the programmer to give a name to each sub-step, which enhances readability, clarity, and debugging-friendliness.I feel dirty just for having written that.
"The sum of a and b is " + (a+b) + "!"
This is completely analogous to your examples. The only disadvantage of this method is the extra quotes, but that's just syntax. You could think that having the literal string split is a disadvantage, but it really isn't - since in the proposal the string has to be literal anyway, so in either case it cannot be a variable.
In general, in what I would consider good language design (syntactic-wise), you either interpret expressions by default and then quote string literals (like most languages do), or you interpret literals by default and then quote expressions (this is the method regular expressions language, or templating languages, use). But you shouldn't do both, it's just a can of worms, especially for code highlighting tools (although in a language with Lisp-like design philosophy - which Python is not - why not, you can do it today with reader macros and whatnot).
> Programming language features should be orthogonal as much as possible.
True, and that's why string building and string formatting should be two things.
By the way, I am not sure about how would this PEP should handle something like
a = 'A1'
b = f'{a}'
a = 'A2'
s = f'{a}' + b
Said otherwise, is it possible to create at run-time an f-string?I was for this PEP until your post made this reality apparent. I'll take security over convenience.
Instead of writing this:
'My name is ' + format(name) + ' my age next year is ' + format(age+1)
or 'My name is {name}, my age next year is {age}'.format(name=name, age=age+1)
You just write f'My name is {name}, my age next year is {age=1}'
It's shorter, it's more readable and more convenient. I can't wait for this PEP to be accepted.Edit: Fixed small errors in the examples.
"Because the f-strings are evaluated where the string appears in the source code, there is no additional expressiveness available with f-strings. There are also no additional security concerns: you could have also just written the same expression, not inside of an f-string."
Of course all current methods of string interpolation in Python have that problem too.
And typing that sentence really, really makes we want to link http://xkcd.com/927/ . I'm unconvinced adding a fourth choice at this very late date can fix anything.
This is a step in the reverse direction. Please don't do this. I am not sure why this is even considered. We already have ways to do this clearly. Let us not add another way to do this in a less readable way that is a lot more easier to write. That is a deadly combination.
Features in python are geared towards more readable code (I know about the stuff you can do with things like comprehensions, but hey I think their power justifies them enough). This will lead to people using this format due to initial convenience, but ends up regretting doing so.
Please remember that code is read more often than it is written. So a requiring a little verbosity if that can enhance readability even a little bit, is good. I hope these kinds of good things about python does not get removed.
I am coming from 9 years of experience with PHP. And I will say that this is not worth it. And this is actually one of the features I have come to like in Python now.
And that is not considering the implications of having expression evaluation inside strings...
Maybe it is popular in other languages - their call, but the fact is, it goes quite wildly against Python design philosophy.
I would also like to note that the are templating systems that let you evaluate arbitrary Python expressions. Perhaps these would be a better choice for users who feel need for this proposal.
There should be one-- and preferably only one --obvious way to do it.
This would be the 4th way of formatting strings in Python.I don't think it would. If I understand this PEP correctly, the "format" method is significantly more dynamic. For instance, I don't think this new PEP would allow for cases where the template isn't stored directly in the program, or where the values to interpolate are not local variables. So you would still need to keep str.format around for those use cases.
'{x} blah blah blah {y}'.format(x=x, y=y)
is more readable than '%s blah blah blah %s' % (x, y)
even if the former is a bit longer.On top of that, there's a whole bunch of stuff you can do with .format() that just isn't possible with %.
Although practicality beats purity.*obligatory "not really, but in most cases" disclaimer
fmt("Hello, {name}! You have {len(msgs)} waiting.")
Interpolates local variables and expressions. It uses the format method, and has all of format's output formatting. >>> def test(x=1):return lambda:say.say("{x}")
>>> test()
NameError: name 'x' is not defined
https://github.com/syrusakbary/interpy seems like a closer solution, could be modified to also support format specsYour example highlights the binding strategy, but more typical would be:
>>> def test(x=1):
... return fmt("{x}")
...
>>> test()
'1'
>>> test(12)
'12'
>>> test('woobers')
'woobers'Yikes!
Please don't tell me that's a function which peeks at the caller's lexical variables, at run time, by name?
I see "inspect.currentframe().f_back" hacks in the code, good grief.
Given a language that doesn't support templated strings inherently, how else would you provide that feature?
Though technically, `fmt` is not a function per se. It's an object with a `__call__` method. That doesn't improve the situation for you, does it?
When it comes to native String interpolation Groovy has it, Scala has it, ES6 has it apparently; to a more limited extent Bash, PHP, Perl have it too of course.
I can't help feeling that other devs are now coming to Python expecting this kind of feature, and are disappointed to find three (harder, often less readable) ways to do it instead. Got to keep up with the Joneses, etc...
A string literal whose value automatically changes with the code surrounding it sounds like a really bad idea.
I also noticed that the PEP uses str.format method as a strawman, ignoring the fact that % string interpolation is very popular and does not need replacing, which is at the core of this problem in the first place; Someone keeps trying to replace something that does not need replacing.
Furthermore, I can't help but think that this would eventually become a complete literal string DSL (if not one already) inside of Python.
I hope this PEP does not get accepted.
> The idea of having a string that is automatically dynamic and whose value is hardly predictable upon first glance, wholly dependent on the stability of the code surrounding it, sounds like an absolutely horrendous idea.
What?! It's not a dynamic string. It's a string concatenation expression with syntactic sugar.
This isn't PHP's register_globals. It's PHP's "{$n + 1}".
What this does, you can already do. "Foo " + bar + " baz" already exists. This is merely nicer syntax.
> "Foo " + bar + " baz"
is not a string literal, neither is one using format or % function. All of those things return dead strings. This pep is about creating a kind of 'live' string literal, which python does not have right now (or need IMHO). So this is not merely a nicer syntax.
Now if only they didn't require Python 3, so I could use them on the production systems I'm working on...
If it's a special node, is it the responsibility of the byte code generator to parse the string? My belief is that it's part of the parser's job, so the AST will never contain an f-string.
What does a syntax error report look like? Or traceback? Will it be able to narrow down the part of the string which causes a problem?
Can f-strings include f-strings, like:
f"{a + (f' and {b+1}')}"
I assume the answer is 'yes, and you shouldn't do that', which I can accept.Support for arbitrary expressions inside of an f-string means that the following is allowed,
def f(a, b):
return f"{yield a} = {b}"
print(f(1, 2))
and will work, and will print something, but it won't be "1 = 2". Nor will any but heavy-weight analysis tools be able to figure out that this 'f' is a generator.I am less happy accepting that a magic string can turn a function into a generator. Take for example this code from around line 438 of https://searchcode.com/codesearch/view/18830026/ :
if attr=='yields' :
yield_unit = self._grab_attr_(obj,'yield_unit')
if yield_unit:
ret = '%s %s'%(ret,yield_unit) # FIXME: i18n?
return ret
The penultimate line could be rewritten, validly, as: ret = f'{ret} {yield_unit}' # FIXME: i18n?
The introduction of a typo, from 'yield_unit' to 'yield unit', would drastically change the function, and be very hard to spot. ret = f'{ret} {yield unit}' # FIXME: i18n?
Yes, Don't Do That, but we know that people use things like syntax highlighters to help understand the code and identify mistakes like this.EDIT: the PEP says that the expression are "parsed with the equivalent of ast.parse(expression, '<fstring>', 'eval')". That means that 'yield' is not allowed.
The Python tokenizer will pass an f-string to the AST builder, which has to pass the string to another tokenizer to generate the new AST. Because f-strings can contain f-strings, this process is recursive. The end result is a new AST that replaces the original f-string.
https://en.wikipedia.org/wiki/Wikipedia:Chesterton's_fence
>In the matter of reforming things, as distinct from deforming them, there is one plain and simple principle; a principle which will probably be called a paradox. There exists in such a case a certain institution or law; let us say, for the sake of simplicity, a fence or gate erected across a road. The more modern type of reformer goes gaily up to it and says, “I don’t see the use of this; let us clear it away.” To which the more intelligent type of reformer will do well to answer: “If you don’t see the use of it, I certainly won’t let you clear it away. Go away and think. Then, when you can come back and tell me that you do see the use of it, I may allow you to destroy it
>>> value = 4 * 20
>>> 'The value is {value}.'.format(value=value)
'The value is 80.'
Even in its simplest form, there is a bit of boilerplate, and the value that's inserted into the placeholder is sometimes far removed from where the placeholder is situated: >>> 'The value is {}.'.format(value)
'The value is 80.'
With an f-string, this becomes: >>> f'The value is {value}.'
'The value is 80.'
Yeah I've had this thought before. >>> 'The value is '+value+'.'
'The value is 80.'
Boilerplate? It's one extra character.I really don't understand why the unnecessary extra "!rsa" modifiers are a good thing though.
They want to keep it for str.format() compatibility, but I'm unconvinced. It hurts readability, and is redundant (There should be one-- and preferably only one --obvious way to do it.)
Why not make a strfmt library on pypi that provides a single fmt(s, args, kwargs) function and let people call that? Why the obsession with more builtins?
def I(s):
import inspect
frame = inspect.currentframe()
caller_locals = frame.f_back.f_locals
return s.format(**caller_locals)
def main():
a = 12
b = 10
print I('A is {a} and B is {b}')
if __name__ == '__main__':
main() # simple quasiliteral, denoted by backticks
$ txr -p '(let ((x "Bob")) `Hello, @{x -10}`)'
"Hello, Bob"
# word-quasiliteral (breaks into list on spaces)
# denoted by hash-backtick:
$ txr -p '#`@(+ 2 2) @(+ 1 2) a b c`'
("4" "3" "a" "b" "c")
# op-argument references in quasiliteral
# ret produces a function whose arguments depend
# on the uses of @1, @2. ... and @rest in the
# expression. The value of the expression is returned.
# These references can emanate from a quasistring:
$ txr -p "(mapcar (ret \`@1--@2--@rest\`) '(1 2 3) '(a b c) '(x y z)))"
("1--a--x" "2--b--y" "3--c--z")
# Very basic indexing, slicing and field adjustment:
$ txr -p '`foo @{(list 1 2 3) [0]} bar`'
"foo 1 bar"
$ txr -p '`foo @{(list 1 2 3) [2]} bar`'
"foo 3 bar"
$ txr -p '`foo @{(list 1 2 3) [1..:]} bar`'
"foo 2 3 bar"
$ txr -p '`foo @{(list 1 2 3) [1..:] 20} bar`'
"foo 2 3 bar"
$ txr -p '`foo @{(list 1 2 3) [1..:] -20} bar`'
"foo 2 3 bar"
Interpolation into quasi-literals is very useful and expressive; I use it all the time. Doing trivial things should look trivial in the code.Oh, right: referencing splices and unquotes is possible from inside a quasistring:
$ txr -p '(let ((a 42) (b (range 1 5)))
^(list `hey @,a @(list ,*b)`))'
(list (sys:quasi "hey "
@42 " " @(list 1 2 3 4 5)))
$ txr -p '(eval (let ((a 42) (b (range 1 5)))
^(list `hey @,a @(list ,*b)`)))'
("hey 42 1 2 3 4 5")
Safe to say, that one's not coming to a Python near you.[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...