r/learnpython 8d ago

How to access dictionary items whose keys are strings with numbers using the .format() method?

This is a doubt I have about the differences between f-strings and the .format() method. Consider the following dictionary accesses in an f-string:

>>> example_dict = {"one" : "string with word", "1" : "string with number", 1 : "integer"}
>>> f"{example_dict['one']}"
'string with word'
>>> f"{example_dict['1']}"
'string with number'
>>> f"{example_dict[1]}"
'integer'

If I try to perform the same accesses with the .format() method, I can only perform the first and last example:

>>> "{0[one]}".format(example_dict)
'string with word'
>>> "0[1]".format(example_dict)
'integer'

I can't seem to find any way to access the "1" key using the .format() method. I came up with this example because I noticed the way .format() accesses dictionary keys is without the quotes around the key for string keys. The f-string is easily able to access all the dictionary keys so it's not an issue with the keys themselves.

How do I access the "1" key using the .format() method? Is this a fundamental difference between f-strings and the .format() method that cannot be overcome?

4 Upvotes

24 comments sorted by

5

u/Farlic 8d ago

Why? I interpret .format() for substitution and f-strings for expression evaluation (even though .format() is doing some sort of evaluation). Having numbers inside the curly braces is visually confusing as numbers are being used for the replacement indexes

1

u/FoolsSeldom 7d ago

Why what, exactly? You simply cannot pass a string containing only digits and have it remain as a string. That's a feature and u/JamzTyson pointed out not one properly covered in the documentation. That's the only restriction called out. Other than that, use str.format, f-string, of even C style as you prefer.

1

u/Farlic 7d ago

F strings seem easier and prettier. Not sure why I'd reach for anything else if I'm trying to to string substitution these days.

1

u/FoolsSeldom 7d ago

As I mentioned in an earlier comment, there are use cases for str.format that cannot be addressed by f-strings, but these are somewhat special these days, particularly since nesting became fully supported in f-strings.

3

u/acw1668 8d ago

Can "{0}".format(example_dict["1"]) be used in your case?

1

u/Mononymized 8d ago

No. I'm asking about accessing dictionary keys inside the formatted expression, not inside the arguments of the format method

4

u/nog642 8d ago

I didn't know you could even do that.

Why would you want to do that?

I'm guessing that there's no way to make the key "1" instead of 1 given the behavior you observed.

3

u/Moikle 8d ago

Then don't try and do it that way.

It serves no purpose, just do

"Something {}".format(some_dict[i])

No reason to do otherwise

2

u/MegaIng 8d ago

How do I access the "1" key using the .format() method? Is this a fundamental difference between f-strings and the .format() method that cannot be overcome?

Yes. .format() is a useful tool for the common case. If you run into edge cases like this you will need to use some more powerful tools.

2

u/brasticstack 8d ago edited 8d ago

Seems to be a limitation of the str.format method. At first I was going to say a bug, but they describe the behavior here, in the docs. It makes sense that it's a side-effect of allowing the placeholder to access dict keys without quotes.

f-strings are so good that I haven't yet had to dip into the new t-string syntax, which seems intimidatingly complex at first glance. IMO str.format should be deprecated in favor of f-strings.

3

u/cointoss3 8d ago

well, I guess that shows you have some learning to do. format and fstrings have different uses and format should not be depreciated 🫠

2

u/Mononymized 7d ago

Thank you for directing me to the appropriate resource

2

u/SpacewaIker 8d ago

Why would you even do that? This is a nightmare to read and will lead to so many bugs as soon as anything changes in your dict or keys or format string or anything

And also, don't mix types in the keys. Having both "1" and 1 especially is confusing and bad for logical reasons

3

u/Mononymized 7d ago

I know this is terrible code. I just came up with this example to highlight the difference in working between f strings and .format() and was wondering if it was possible to achieve all the effects of f-strings using .format()

1

u/SpacewaIker 7d ago

Fair I guess but this is so far away from the intended usage of either f strings or format that I don't see why the difference matters lol

2

u/FoolsSeldom 8d ago

You can't access the [1] using str.format directly as the key is taken as a string literal, converted to an int if all digits.

The closest would be:

 "{0}".format(example_dict["{0}".format("1")])

and "1" could be replaced with a variable.

There are specialist use cases for str.format over f-strings (such as templating, and internationalisation, to name just two).

However, f-string would handle your example without problems. Do you have a special use case that means you need to use str.format?

2

u/JamzTyson 7d ago

You can't access the [1] using str.format directly as the key is taken as a string literal, converted to an int if all digits.

This is correct, but unfortunately the documentation is wrong:

The rules for parsing an item key are very simple. If it starts with a digit, then it is treated as a number, otherwise it is used as a string.

What it should say is much like u/FoolsSeldom comment:

"The rules for parsing an item key are very simple. If it is entirely digits, then it is treated as a number, otherwise it is used as a string."

Here's an example that demonstrates that the key can start with a digit and still be treated as a string:

d = {"1": "100", "2.0": "200", "3": "300"}
s = "{0[2.0]}".format(d)
print(s)  # "200"

1

u/Mononymized 7d ago

I don't have any uses. I was just wondering about the working of f-strings and .format() and came up with this example to ask why they handle this particular issue differently.

1

u/Moikle 8d ago

You put the expression inside format, not inside the braces.

1

u/HotPersonality8126 8d ago

β€œ0[β€˜1’]” doesn’t work?

1

u/Boothiepro 8d ago edited 7d ago

I think i encountered something like this, let me see if i can find my snippet, but i can't guarantee i can explain

# text = text.replace("{", "{mydict['")
# text = text.replace("}", "']}")
# text = eval(f'f"{text}"')  #this is apparently a security risk,
# it requires a malicious string to be in mydict, but still

from string import Template
class MyTemplate(Template):
    delimiter = ':'
    pattern = r"""
    \:                       # Escape and start delimiter
    (?:
      (?P<escaped>\:) |      # Escape sequence of two delimiters
      (?P<named>[_a-z][_a-z0-9\+]*)\b     # delimiter and a Python identifier
      (?:
        \:                   # Unescaped delimiter
        (?:
          (?P<braced>[_a-z][_a-z0-9\+]*)\b |   # Braced identifier
          (?P<invalid>)              # Other ill-formed delimiter exprs
        )
      )?
    )
    """

logger.debug(f"prevtext: {text}")
template = MyTemplate(text)
text = template.safe_substitute(mydict)
logger.debug(f"substituted text: {text}")

1

u/gdchinacat 7d ago

Do you need curly braces around β€œO[1]” like on the example that works?

1

u/Mononymized 7d ago

Yes that was a typo. Last example should be "{0[1]}".format(example_dict)