Dictionary in Format String Syntax versus f-string

I have a question. What will the following code print?

>>> m = {"key": "value"}

>>> f"{m['key']}"
>>> f"{m[key]}"

>>> "{m['key']}".format(m=m)
>>> "{m[key]}".format(m=m)

The f-string example makes perfect sense to me. It will print the correct value in the first line and raise a NameError in the second:

>>> f"{m['key']}"
'value'
>>> f"{m[key]}"
NameError: name 'key' is not defined

What about the format string:

>>> "{m['key']}".format(m=m)
KeyError: "'key'"
>>> "{m[key]}".format(m=m)
value

In this case, the first line, that looks like a dictionary access with __getitem__, will raise a KeyError, while the second line will print the value correctly, but many would say that it should raise a NameError. This feels very counterintuitive.

By reading the Format String Syntax I understand that element_index should not be surrounded by quotes.

I’m curious to understand a little bit more about this so I’d like to ask the following questions:

  1. Does anyone knows why was decided to not use quotes when accessing dictionary values in Format String Syntax? This choice seems to be more difficult to implement than the f-string and also limits the use of closing square brackets.
  2. Is there any plans to update this syntax? I think that the f-string version is more coherent.
  3. I’ve had difficulties finding any resources explaining that dictionary access in Format String Syntax should be made without quotes. It’s not explained in the official documentation and almost every other article just expands the dictionaries with **mapping. Is there any interest of making this more clear in the documentation?

Related Stack Overflow topic (found by searching [python] element_index).

3 Likes

It doesn’t seem like this is the actual question, because you apparently already tried it and then asked more questions about the result :wink:

Because it’s a special-case syntax that is specifically intended not to work like a Python expression, but instead just give certain limited access to elements of the passed-in value. You might also have noticed, for example, that .format can work by specifying the arguments positionally by number, or implicitly. That allows for syntax that makes no sense at all in a Python expression:

>>> data = {'one': {'two': 'three'}}
>>> "{0[one][two]}".format(data)
'three'
>>> "{[one][two]}".format(data)
'three'

Similarly with attributes - one example in the documentation you linked:

"Weight in tons {0.weight}"       # 'weight' attribute of first positional arg

No, first because it was specifically designed this way and specifically is not meant to work the way f-strings do, and second because it would break huge amounts of existing code.

The appropriate starting point is the documentation for the str type itself, in the ā€œbuilt-in typesā€ section of the library reference. The documentation of the format method then links us to the format string syntax that you found (for historical reasons this is held within the documentation for the now rarely-used string standard library module).

In your example, it’s not an element_index but a field_name, and indeed the documentation says:

Because arg_name is not quote-delimited, it is not possible to specify arbitrary dictionary keys (e.g., the strings ā€˜10’ or ā€˜:-]’) within a format string.

I don’t know what you mean by ā€œit’s not explained in the official documentationā€, because this is the official documentation and it’s right there.

To my understanding, the documentation team is always happy to receive contributions with suggestions for improved wording. You may also want to check out the Documentation section of the forum.

4 Likes

That clarifies a lot! Thank you! But I’m still a little confused about the documentation :`)

Correct me if I’m wrong, but I think that this is the setup for my example:

>>> "{m[key]}".format(m=m)
# Fields
# m[key] => field_name
#      m => arg_name
#    key => element_index

My issue is that It’s hard to understand, other than by looking at the grammar table, that key should not be quoted.

Indeed the relevant part of the documentation is the following phrase after the one you quoted:

The arg_name can be followed by any number of index or attribute expressions. An expression of the form '.name' selects the named attribute using getattr(), while an expression of the form '[index]' does an index lookup using __getitem__().

But not even this section explains that index should not be quoted when accessing keys in a dictionary, as it is likely focusing in integer indexes for accessing sequences.

There’s also examples with the __getitem__() and getatt() :

"Weight in tons {0.weight}"       # 'weight' attribute of first positional arg
"Units destroyed: {players[0]}"   # First element of keyword argument 'players'.

This leads me to think that __getattr__() acces identical to python’s syntax. So the following example should work:

"Book Title: {book['title']}"     # Value for key 'title' in book dictionary

When in fact it should not be quoted:

"Book Title: {book[title]}"       # Value for key 'title' in book dictionary
1 Like

It’s a quirk I first read about on one of the core developer mailing lists at the time f-strings were being discussed as a proposed feature. The core developers are aware of it. It’s not just you who think it looks odd :smiling_face:

1 Like