7

f-strings don't behave nicely when used with dictionaries, as mentioned here.

Here is an example of the not-so-nice behavior:

d = {'foo': 'bar'}

# Both work as expected
d["foo"]
d['foo']

# This only works when different quotations are used in the inner and outer strings
f'{d["foo"]}'
f"{d['foo']}"

# This doesn't work
f'{d['foo']}'
f"{d["foo"]}"

# The .format() method doesn't care
'{}'.format(d['foo'])

The last two f-strings listed result in a SyntaxError: invalid syntax, which happens because the string '{d['foo']}' is evaluated as '{d['foo']}'.

What is the underlying reason everything inside the curly brackets of f-strings doesn't get evaluated separately, as when using the old .format() method, and what could possibly be the reason for implementing f-strings in this way?

I love f-strings, but this seems like a point in favor of the old method.

  • 2
    How would you mark the end of the string if `'` didn't mean `'`? – Peter Wood Nov 23 '18 at 15:34
  • Another point in favor of `format` is that you can omit the string delimiters surrounding index keys, e.g., `'{}'.format(d[foo])`, unless they contain a number (or are one), strange as that is. – Magnus Lind Oxlund Feb 13 '20 at 18:58

3 Answers3

6

F-strings are literal strings. Including unescaped quotes within quotes (of the same type) is invalid syntax. This makes sense, since the result is ambiguous: the interpreter will not know when the string should end. One traditional way of including quotes within quotes is to use a backslash. But PEP498 forbids backslashes in expressions within f-strings:

Backslashes may not appear inside the expression portions of f-strings...You can use a different type of quote inside the expression...

Therefore, the only way left to access a dictionary value given a key in an f-string expression is to use a different type quote. Using single quotes, or double quotes, everywhere is ambiguous and gives SyntaxError.

str.format is a regular method, and as such works differently: d['foo'] is evaluated before the string is constructed. Just like when you feed arguments to a function, the arguments are evaluated before the function does anything.

jpp
  • 159,742
  • 34
  • 281
  • 339
  • Your answer explains why `format` expressions are evaluated separately, but it doesn't address why f-string expressions are not. – Magnus Lind Oxlund Jan 28 '20 at 14:32
  • 1
    @MagnusLindOxlund, I have updated my answer and believe it adequately answers the question, which focuses on how to incorporate quotes within quotes. – jpp Jan 28 '20 at 17:59
  • 1
    I agree that it shows how to solve the problem, and I've removed the downvote from your answer, but the question asks for the reason behind the design. I've tried to answer that. – Magnus Lind Oxlund Feb 13 '20 at 18:55
2

This has nothing to do with f-strings. f strings are common strings once evaluated. What you are trying would be a problem with standard strings too

The problem is that

'a "b" c' 

is declares the literal a "b" c

while

'a 'b' c'

the quotes close and reopen. So, it is equivalent to string a, followed by variable b, followed by string c.

That's the whole reason python supports both types of quotation marks

blue_note
  • 27,712
  • 9
  • 72
  • 90
  • F-strings are common strings once evaluated, but not before they're evaluated. Their whole raison d'être is to suspend the constraints of string literals while composing them to synthesize strings programmatically. The question is: why are f-strings subject to string delimiters when we have braces to mark expressions within them? – Magnus Lind Oxlund Jan 28 '20 at 15:21
0

According to PEP 498, f-strings rely on the implementation of regular string literals, and as such are subject to the same constraints. In addition to those, f-strings have their own constraints, such as the exclusion of backslashes anywhere within expressions.

A rationale for the choice of implementation has not been published, but ease of implementation, backwards compatibility, syntax highlighting, and consistency with existing interpolation syntax all featured in discussions among core developers on the publicly accessible mailing lists.1

A central point of contention was whether the proposed concept constitutes a string with special properties or a string interjected with code.2 The former view was favored. PEP 536, embodying the dissenting view and additionally seeking to lift several syntactic constraints, was subsequently filed.

Based on this discussion, a tentative compromise of prohibiting backslashes within expressions was agreed upon, leaving string delimiter inversion as the only remaining option for indexing dictionaries within f-string expressions.


  1. Select discussions predating f-string introduction (Python 3.6):

    [Python-ideas] Briefer string format
    Re: [Python-ideas] Briefer string format
    Re: [Python-ideas] Briefer string format
    [Python-ideas] String interpolation for all literal strings
    [Python-Dev] PEP-498: Literal String Formatting
    [Python-Dev] PEP 498 f-string: is it a preprocessor?
    [Python-Dev] PEP 498 href="interpolated f-string" tweak
    [Python-Dev] Parsing f-strings from PEP 498 -- Literal String Interpolation

  2. [Python-ideas] Let’s make escaping in f-literals impossible
Magnus Lind Oxlund
  • 304
  • 1
  • 6
  • 19
  • But f-strings shouldn't even see those backslashes, at least a classical lexer wouldn't. So, they are a weird, *very* weird (and unpythonically ugly) hybrid. – Jürgen A. Erhard Oct 09 '20 at 04:47