Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set the default subtitles language in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please see our video player troubleshooting guide to resolve the issue.

The <name> and <conversion> Components

00:00 From the last lesson, replacement fields might’ve seemed really simple—just a pair of braces or a pair of braces maybe with an index or a keyword inside them.

00:08 But really, replacement fields are incredibly flexible and allow you a lot of opportunity to format and convert your different strings that you’re trying to pass in in a myriad of ways. Replacement fields have three parts: a name, a conversion, and a format specification. In this lesson, I’ll talk about the name and conversion aspects of it.

00:30 As I said, replacement fields of their own syntax following the format <name>, and then an exclamation point (!) followed by a <conversion> specifier, and then a colon (:) followed by a <format_spec> (format specifier). So the name, as you’ve seen a little bit of already, is either nothing, an index in a list of positional arguments, or a keyword to access a keyword argument.

00:50 And something that you haven’t seen so far is that they support list indices, dictionary key references, or object attributes. So you can access all of these things within your actual string replacement fields.

01:02 The conversion is simply the method used to convert the object to a string. By default, that is !s for str() (string), and then you can also use r for repr() and a for ascii().

01:16 And if you don’t know too much about these methods, these are three different ways of converting items to strings, and Python has them so that you can have different levels of readability for your string conversions. So for example, str(), normally what is the .__str__() method inside an object, is supposed to be a really human-readable string.

01:38 Whereas repr() is supposed to be more of a uniquely identifiable string. And ascii(), of course, has to correspond to the ASCII set of character codes, so it can’t include some Unicode characters. So with all that said, let’s head over into the REPL and see how these actually work.

01:55 First, I’ll demonstrate how the <name> field of a replacement field can use dictionary accesses and get object attributes without any troubles at all.

02:05 I’ve created this example dictionary, which is a really completely faithful description of me. And I’m just going to put in here, I’m going to say something like 0 at name.

02:18 This will be a preliminary little run here to say .format(). And I’ll have to pass in, of course, the example_dict. And that will give you my "name", which is 'Liam'. As you can see, you can access the dictionary keys of a dictionary object through the actual indices of your arguments. You can also access them, of course, using keywords. So if you say something like dict—actually, I won’t use that because that’s a built-in, I’ll just say d. So I’ll say d=example_dict and then I’ll say d at name and it gives the exact same result.

02:53 As long as you provide some way to get the specific argument here, then you can easily access dictionary fields of your argument. Of course, just make sure it’s a dictionary, because if what you pass in isn’t a dictionary, then you’ll get an error here.

03:07 And if I wanted to make a nice little description of the whole dictionary, I could say "Dict: ", and then I could say d at name with a comma, d at age,

03:22 and then finally d at height. And this would give you a nice little concise representation of this whole dictionary. And of course I just have to pass in d=example_dict.

03:35 And it says 'Dict: Liam, old, kinda tall'. That’s me. Of course, you can do all of the same things that I just showed with dictionaries with lists as well, just using numeric indices instead of dictionary keys. Cool!

03:49 So now let’s take a look at object attributes, which work in much the same way. I’m going to clear the screen and create a little object class, a little container class for demonstration. So here I am, and I I have a Container class, and as you can see, it takes in two parameters, an x and a y, and it just stores their values.

04:07 And then it also has a .__repr__() and a .__str__() method. And of course, .__repr__() stands for a representation or something like that.

04:14 And those are to show off, in just a second, the <conversion> field of your replacement field. So if I instantiate a Container and then I say c = Container(), and I’ll just pass in some random numbers, 5 and 6.

04:28 Then I can say here, .format() and I can originally pass in c, and it will give me 'Str of Container'. I’ll talk more about that in a second.

04:40 But I can also say something like "{0.x}" and it’ll give me just the x attribute of c. Or I could say "{c.y}" and as long as I make this a keyword argument with c=c, then I will get this '6' again—or the attribute field, which is the value of y, which is 6.

05:03 So this works much in the same way that a dictionary accessing does, just you’re using dot notation instead of a bracket notation. And again, it looks kind of weird to say "{0.x}"just remember that this argument will be replaced directly here when this function is called. Now I’ll show you how I can do this same thing here, and I can pass in this, but I can actually change what representation function is called.

05:28 I could say "{!r}" and it will give me 'Repr of Container'. And I could pass in "{!s}" and it will get the default, which is 'Str of Container'.

05:39 And then I haven’t actually implemented an ASCII method here, but I could pass in an ascii() version and it would give me the .__repr__(), but it’ll give me the .__repr__() converted to ASCII characters.

05:51 So if I put some weird character in 'Repr of Container' that wasn’t representable in ASCII, it would instead give me strange combination. Something like, you know, '\a12f', or something, which would be a key for that symbol because that symbol is just not representable in ASCII.

06:07 So those are the different ways you can convert these things, and I can also put in something like, I could say "{0.x!r}" and this would give me the repr() representation of '5'. Unfortunately, though, integers aren’t very useful as demonstrations here because they have the same str(), repr() and ascii()—it’s all just exactly the same thing.

06:27 But that’s just to show you that you have this <name> and then you follow it with an exclamation point (!) and then your <conversion> specifier there, and it all works out just fine. Next up, I’ll talk about the format specifier, which is a really detailed way to get exactly the formatting you want from your strings.

mic on Dec. 3, 2020

Thank you Liam. I really like your lessons. You are the teacher whose pronunciation I understand best!

How to print the dictionary element with the string key in the following (stupid) example?

>>> mydict = {'1': 'one', 1: '2nd one'}
>>> "{d[1]}".format(d=mydict)
'2nd one'

Bartosz Zaczyński RP Team on Dec. 3, 2020

@mic I’d recommend using f-strings:

>>> mydict = {'1': 'one', 1: '2nd one'}
>>> f"{mydict['1']}"
'one'
>>> f"{mydict[1]}"
'2nd one'

mic on Dec. 3, 2020

Thank you very much for the quick answer! This means there is a ‘gap’ regarding this in the format()- method!?

DoubleA on Feb. 16, 2021

@Bartosz Hi there!

Am I right saying that the <string>.format() method and f-strings have slightly different syntax when it comes to accessing values of a dictionary?

my_dict = {'one': 1, 'two': 2, 'three' : 3}

#1: accessing values of all the 3 keys using the .format() method:

print('{0[one]}, {0[two]}, {0[three]}'.format(my_dict))
--> 1, 2, 3

#2: accessing values of the same dict keys by using an f-string:

print(f"{my_dict['one']}, {my_dict['two']}, {my_dict['three']}")
--> 1, 2, 3

If I use the conventional ‘square bracket notation’ to access a value of a key with the .format() method an exception invalid syntax is raised. However, the []-notation works perfectly fine with f-strings. Why is this so? This only adds confusion (IMO), unless there’re reasons.

DoubleA on Feb. 16, 2021

What would be real-life examples of using the !a conversion specifier?

Bartosz Zaczyński RP Team on Feb. 16, 2021

@DoubleA The main difference between "".format() and f-strings is that the latter lets you evaluate any legal Python expression placed between the curly brackets, whereas the former only parses a plain string that must conform to a mini-language syntax.

You can refer to any variables present in your current scope within an f-string:

>>> fruits = {"apples": 14, "bananas": 3, "oranges": 0}
>>> f"There are {sum(fruits.values())} fruits in the basket"
'There are 17 fruits in the basket'

Notice how a piece of Python code is embedded directly in the string literal. However, with string formatting, you’re constrained to using replacement fields:

>>> "There are {num_fruits} fruits in the basket".format(num_fruits=sum(fruits.values()))
'There are 17 fruits in the basket'

As to your second question, using the !a format specifier inside a string template will call the built-in ascii() function on the arguments passed to the .format() method:

>>> "{!a}".format("naïve café")
"'na\\xefve caf\\xe9'"

It converts the argument to a printable string representation by escaping non-ASCII characters with their byte representation. It could be useful to safely serialize data before sending it over the network or saving it to a file.

DoubleA on Feb. 16, 2021

@Bartosz Thank you for the elaborate explanation. It seems now that f-strings allow to write more concise and easier-to-read code when compared to "".format() method, other things being equal.

useeme2ndtime on April 24, 2021

Nice explenation Bartosz!

Become a Member to join the conversation.