0

I was working on a project and encountered an interesting situation. I'm reading some data from a file and use them to format/complete a string like this.

#After I read the file I create an internal dictionary to use, so for brevity 
#I'll use an example of the retrieved data
film = {"name":"Gran Torino", "type":"Drama"}

movie = "{name} is a {genre} movie. The vote given by {person} is {stars}"
movie = movie.format(name=film["name"],genre=film["type"])

This obviously generate an error, this one:

Traceback (most recent call last):
File "movie.py", line 65, in <module>
    movie = movie.format(name=film["name"],genere=film["type"])
KeyError: 'person'

because some keys are missing. The missing one will be retrieved from user when he will input them. I already know some way to evade the error, like format everything after I retrieved the other data from the user input.
However, I'm curious about something:

Is there a way in Python, using .format()/f-strings/string Template, for formatting a string with some initial value and maintain some missed places a later second passage? In other words can I insert only certain value and later the missing one?

Pseudo-code of what I try to achieve

string = "{first} text {second} ... and so on {last}"
#apply one time format but only one some place-holders
result = string.format(first=value) #dosen't need to generate an error
print(result) # "value text {second} ... and so on {last}"

Like you can see what I want is to know if in the language there is a way of doing this procedure with the built-in formatting options and without create a new function for this.

Side note:

I already tried to use some other options like string Template but wasn't able to make it work. Moreover, I know that is possible first concatenate the data in my possession, later add a string part and format later like this:

first_part = film["name"] + " is a " + film["genre"] + " movie."
second_part = "The vote given by {person} is {stars}"
movie = first_part + second_part
#later when I have stars and person
movie.format(person=v1,stars=v2)

This works, but I would love to understand if I can use two times format() method instead.

Community
  • 1
  • 1
Iulian
  • 300
  • 2
  • 8
  • Possibly related https://stackoverflow.com/questions/53670748/python-3-6-formatting-strings-from-unpacking-dictionaries-with-missing-keys – snakecharmerb Dec 08 '18 at 19:29

1 Answers1

0

You could write a function that constructs the string for you. By default, it will use "unknown" as a string if an information is missing:

def str_constructor(name="unknown", genre="unknown", person="unknown", stars="unknown"):
    movie = "{} is a {} movie. The vote given by {} is {}".format(name, genre, person, stars)
    return movie

film = {"name":"Gran Torino", "type":"Drama"}
str_constructor(name=film["name"], type=film["type"])

Update:

Now that I fully understand your desired outcome, I have another option. An intuitive option to this would be:

string = "{name} is a {genre} movie. The vote given by {person} is {stars}"
film = {"name":"Gran Torino", "type":"Drama"}
print string.format(**film)

But this will raise an error, since there are variables missing in your formatting-dictionary.

In this answer a solution is given by constructing an individual formatter. Try this:

from string import Formatter

class UnseenFormatter(Formatter):
    def get_value(self, key, args, kwds):
        if isinstance(key, str):
            try:
                return kwds[key]
            except KeyError:
                return key
        else:
            return Formatter.get_value(key, args, kwds)

string = "{name} is a {genre} movie. The vote given by {person} is {stars}"
film = {"name":"Gran Torino", "type":"Drama"}

fmt = UnseenFormatter()
print fmt.format(string, **film)

...and you get:

Gran Torino is a genre movie. The vote given by person is stars

Later you can update your film dictionary to deliver the rest of your result.

offeltoffel
  • 2,691
  • 2
  • 21
  • 35
  • Sorry man is not what I want. I don't want the missing value be set to unknown, but to stay `{}`, so when I call second time `.format(...)` on the string they are there without any more work then needed. However, appreciated the idea. Probably I'll use it in future projects. – Iulian Dec 05 '18 at 21:48
  • No problem, maybe we can still get the problem solved. Are you aware that you can build nested formatters? The formatted strings can be formatters as well. I use this when I want to align an integer in a string, like `"{:>7}".format("({})".format("T20")))` -> `" (T20)"`. – offeltoffel Dec 06 '18 at 11:46
  • Also, if you don't want it to be "unknown", you could just use "" which means, it is omitted. Isn't that what you need? – offeltoffel Dec 06 '18 at 11:49
  • Yeah I know that you can pass strings for parmaters and so the returned ones from a `format()` call too. I have some way to make it works the way you're suggesting like bulding a function. However I would like know if there is a built-in approach in the language to pass only part of the paramters when I don't have them all. I don't want to change `"{person}"` in something else, but let it stay there untili I pass the value for it and not changin it in "" or "unknown". – Iulian Dec 06 '18 at 14:18
  • I'm pretty sure there isn't. You are specifying a string, reserving space for a variable "person". Later you don't tell python what "person" is, so it can't work. In any regard it should be possible to give python a value for "person" even if it's not the final one. Let person be a default "" and change it to a real name as soon as it's available. – offeltoffel Dec 06 '18 at 14:27
  • Yeah I'm aware of this approach however I wanted to know if there was some way using format() and tell it, don't touch "{person}" o "{stars}" place-holder for now. You can edit your answer with this final thoughts and if no one will give some other answer in some days I will accept your one. – Iulian Dec 06 '18 at 14:33
  • Updated my answer... (last try ;)) – offeltoffel Dec 06 '18 at 14:41
  • There is the problem of loosing brackets "{}" in the returned string so the second call on fmt.format() isn't working, but what I wanted is to work with default formatting options not creating my own. Obviously I can use some other option after, like replace the substring with another one, but this isn't ideal. However, I'll accept your answer after some days so maybe there will be some other ideas from the community. – Iulian Dec 06 '18 at 15:06
  • You don't have to "accept" the answer if it's not what you were finally looking for. Maybe someone else will find the question and shed new light upon the problem. I wish you the best of luck for that :) – offeltoffel Dec 06 '18 at 15:09