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.
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!
Naresh on Sept. 1, 2021
dict_one = {
"name": "student",
"class": 6,
"roll_no": 12345,
"value": False,
1: True
}
print("{[name]}".format(dict_one))
print("{0[name]}".format(dict_one))
Could anyone explain me the importance of prefixing 0 to the keyword in {}
as I could get the same result WO using it.
Martin Breuss RP Team on Sept. 1, 2021
Hi @Naresh! You can leave out the 0
, which is also called manual field specification. You use these numbers to explicitly tell Python which arguments that you’re passing to .format()
it should input at what place in your string.
Because you can define which one should go where with their 0-based-indicies, you can swap them around, or use them more than once:
>>> d1 = {"name": "student"}
>>> d2 = {"name": "teacher"}
>>> print("{0[name]} {1[name]} {0[name]}".format(d1, d2))
student teacher student
If you leave out the manual field specifications like you showed in your example above, that’s fine for Python. It’ll use automatic field numbering instead:
>>> d1 = {"name": "student"}
>>> d2 = {"name": "teacher"}
>>> print("{[name]} {[name]}".format(d1, d2))
student teacher
This works fine as long as you keep the arguments and the string replacements equal length and in the order that you want to substitute them.
If you want to do anything more fancy, you’ll run into errors when you use automatic field numbering:
>>> d1 = {"name": "student"}
>>> d2 = {"name": "teacher"}
>>> print("{[name]} {[name]} {[name]}".format(d1, d2))
IndexError: Replacement index 2 out of range for positional args tuple
In short, as long as you keep it simple, you can leave off the index-based numbering. If you want to do more complex string formatting using .format()
, then it’s best to use the manual field specifications.
And, as Tim Peters writes in the second line of The Zen of Python:
Explicit is better than implicit.
Since it doesn’t hurt to add the explicit numbering and can make your code better readable, I’d suggest to add it in most cases.
Naresh on Sept. 2, 2021
Hi Breuss, perfect, thanks a lot for detailed explanation!. I feel encouraged to post questions on upcoming topics as I am on the way to learn.
Become a Member to join the conversation.
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?