[Python-ideas] Briefer string format

Chris Angelico rosuav at gmail.com
Mon Jul 20 04:42:04 CEST 2015


On Mon, Jul 20, 2015 at 11:33 AM, Steve Dower <Steve.Dower at microsoft.com> wrote:
> Chris Angelico wrote:
>> On Mon, Jul 20, 2015 at 10:43 AM, Steve Dower <Steve.Dower at microsoft.com> wrote:
>> It'd obviously have to be a compile-time transformation. My point is
>> that it would, unlike all other forms of literal, translate into a
>> function call.
>
> Excluding dictionary literals, of course. And class definitions. Decorators too, and arguably the descriptor protocol and __getattribute__ make things that look like attribute lookups into function calls. Python is littered with these, so I'm not sure that your point has any historical support.
>

Dictionary/list display isn't a literal, and every time it's
evaluated, you get a brand new object, not another reference to the
same literal. Compare:

>>> def make_list(): return [1,2,3]
>>> make_list() is make_list()
False

Class and function definitions are also not literals, although people
coming from other languages are often confused by this. (I've seen
people write functions down the bottom of the file that are needed by
top-level code higher up. It's just something you have to learn -
Python doesn't "declare" functions, it "defines" them.)

Going the other direction, there are a few things that you might think
are literals but aren't technically so, such as "2+3j", which is
actually two literals (int and imaginary) and a binary operation; but
constant folding makes them functionally identical to constants.

The nearest equivalent to this proposal is tuple display, which can
sometimes function almost like a literal:

>>> def make_tuple(): return (1,2,3)
>>> make_tuple() is make_tuple()
True

This disassembles to a simple fetching of a constant. However, it's
really just like list display plus constant folding - the compiler
notices that it'll always produce the same tuple, so it optimizes it
down to a constant.

In none of these cases is a string ever anything other than a simple
constant. That's why this proposal is a distinct change; all of the
cases where Python has non-constants that might be thought of as
constants, they contain expressions (or even statements -
class/function definitions), and are syntactically NOT single
entities.

Now, that's not to say that it cannot possibly be done. But I
personally am not in favour of it.

>> How is your "x=x, y=y" version materially different from explicitly
>> mentioning locals() or globals()? The only significant difference is
>> that your version follows the scope order outward, where locals() and
>> globals() call up a specific scope each.
>
> Yes, it follows normal scoping rules and doesn't invent/define/describe new ones for this particular case. There is literally no difference between the function call version and the prefix version wrt scoping.
>
> As an example of why "normal rules" are better than "locals()/globals()", how would you implement this using just locals() and globals()?
>
>>>> def f():
> ...     x = 123
> ...     return [f'{x}' for _ in range(1)]
> ...
>>>> f()
> ['123']
>
> Given that this is the current behaviour:
>
>>>> def f():
> ...   return [locals()[x] for _ in range(1)]
> ...
>>>> f()
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
>   File "<stdin>", line 1, in f
>   File "<stdin>", line 1, in <listcomp>
> KeyError: 'x'

Sure, that's where following the scoping rules is better than
explicitly calling up locals(). On the flip side, going for locals()
alone means you can easily and simply protect your code against
grabbing the "wrong" things, by simply putting it inside a nested
function (which is what your list comp there is doing), and it's also
easier to explain what this construct does in terms of locals() than
otherwise (what if there are attribute lookups or subscripts?).

>> Will an f"..." format string be mergeable with other strings? All the
>> other types of literal can be (apart, of course, from mixing bytes and
>> unicode), but this would have to be something somehow different.
>
> I don't mind saying "no" here, especially since the merging is done while compiling, but it would be possible to generate a runtime concatentation here. Again, you only "know" that code (currently) has no runtime effect because, well, because you know it. It's a change, but it isn't world ending.
>

Fair enough. I wouldn't mind saying "no" here too - in the same way
that it's a SyntaxError to write u"hello" b"world", it would be a
SyntaxError to mix either with f"format string".

>> In every way that I can think of, this is not a literal - it is a
>> construct that results in a run-time operation.
>
> Most new Python developers (with backgrounds in other languages) are surprised that "class" is a construct that results in a run-time operation, and would be surprised that writing a dictionary literal also results in a run-time operation if they ever had reason to notice. I believe the same would apply here.
>

That's part of learning the language (which things are literals and
which aren't). Expanding the scope of potential confusion is a
definite cost; I'm open to the argument that the benefit justifies
that cost, but it is undoubtedly a cost.

>> A context-dependent operation, at that.
>
> You'll need to explain this one for me - how is it "context-dependent" when you are required to provide a string prefix?

def func1():
    x = "world"
    return f"Hello, {x}!"

def func2():
    return f"Hello, {x}!"

They both return what looks like a simple string, but in one, it grabs
a local x, and in the other, it goes looking for a global. This is one
of the potential risks of such things as decimal.Decimal literals,
because literals normally aren't context-dependent, but the Decimal
constructor can be affected by precision controls and such. Again, not
a killer, but another cost.

>> That's why I'm -1 on this looking like a literal.
>
> I hope you'll reconsider, because I think you're working off some incorrect or over-simplified beliefs. (Though this reply isn't just intended for Chris, but for everyone following the discussion, so I hope *everyone* considers both sides.)
>

Having read your above responses, I'm now -0.5 on this proposal. There
is definite merit to it, but I'm a bit dubious that it'll end up with
one of the problems of PHP code: the uncertainty of whether something
is a string or a piece of code. Some editors syntax-highlight all
strings as straight-forward strings, same color all the way, while
others will change color inside there to indicate interpolation sites.
Which is correct? At least here, the prefix on the string makes it
clear that this is a piece of code; but it'll take editors a good
while to catch up and start doing the right thing - for whatever
definition of "right thing" the authors choose.

Maybe my beliefs are over-simplified, in which case I'll be happy to
be proven wrong by some immensely readable and clear real-world code
examples. :)

ChrisA


More information about the Python-ideas mailing list