Why Do 5 + "5" and "5" + 5 Give Different Errors in Python? • Do You Know The Whole Story?
If __radd__() is not part of your answer, read on…
5 + "5"and
"5" + 5give a different error message because the first calls the
__add__()dunder method whereas the second calls the
…is not a sufficient answer.
I’ve often given this explanation when teaching. And it’s not wrong. I’ll still use it in introductory courses.
But a serendipitous exploration led me down a different route recently…
__add__() Alone Doesn't Fully Explain This
Let's start by looking at the error messages for the two versions:
In the first example, the integer
5 comes first, before the
+ operator. The string
"5" is after the
+. This raises a
TypeError, which tells you the types you used with the
+ operator are unsupported.
You can't add the string
"5" to the integer
5. This probably makes sense.
In the second example, the order of the data types is swapped. The string
"5" is before the
+ operator and the integer
5 after. You still get a
TypeError, but the message is different. This error message is specific about concatenation of strings. You can only add a string to another string.
+ operator calls the
__add__() special method of the first operand—the object on the left of the
+ operator. Special methods are methods that allow objects to be used with several operators and built-in functions. The special method names start and end with double underscores, leading to their informal name of dunder methods. I'll be writing in more detail about classes in the near future.
In the expressions you tried to evaluate above, the first operand is an integer in one example and a string in the other. This should explain why the error messages are different, right?
Yes, but only in part.
Let's start with the version where the string is the first operand:
"5" + 5
This expression is the same as the following:
This is the same error message you get when you try to evaluate
"5" + 5. So, what's the whole fuss, you may be thinking?
Let's try the same with the expression in which the integer comes first:
5 + "5"
But before we can get to the main point of this discussion, you encounter an issue:
There's the 'inconvenience' of a
SyntaxError since the
. after a number is used to denote floats and not to access attributes of the
int class. There are a few workarounds to this. Here's the clearest one:
You no longer need to use the integer literal
5 directly to call the
__add__() special method. Great! That problem is solved.
However, you don't get the same error message as when you evaluate
5 + "5". When you call the
__add__() method directly, it returns
NotImplemented. This is a special value and the only instance of the
We'll soon carry on down the path this leads us. But first, you may be curious about the workarounds I mentioned earlier to deal with the issue that you can't write
5.__add__("5"). You've seen the solution in which you define a variable name first. You can also call
__add__() directly by using the class name:
You need to include both objects as arguments since you're not calling
__add__() as a method on an integer object. The other option is a "cheat" I've only learned about recently. You can add a space after the integer literal and call the method without the need for a variable name:
Note the space between
. in this version. It doesn't matter which option you choose. They do the same thing.†
† Edit: Thanks to those who pointed out there’s another option:
Here's what's missing in this discussion so far. Warning: dense sentence coming up next! When the
__add__() special method returns
NotImplemented, the interpreter falls back to the second operand's reflected addition operator. A lot is happening in that sentence. Let's unpack it:
5 + "5"first calls the
__add__()special method in the
This call returns
The interpreter next looks at the second operand, which is a string in this case. It calls its
__radd__()special method. This is the addition special method with reflected operands.
5 .__add__("5") returns
NotImplemented, the interpreter gets its cue to call
"5".__radd__(5). Note that this is the
__radd__() method for the string class. Let's see what's the output:
The string class doesn't have a
__radd__() method. Strings can only be added to other strings.
str.__radd__() can deal with adding integers to strings. At this point, the interpreter raises the
TypeError you got earlier:
unsupported operand type(s) for +: 'int' and 'str'.
Let's Try To Multiply Instead
Let's try a similar approach using multiplication instead of addition:
The behaviour is different in this case. Both versions give the same result: the string containing the character
"5" is repeated five times.
Let's drill into both of these expressions, starting with the one where the string is the first (or left) operand.
The dunder method associated with the
* operator is
"5" * 5 starts by calling the
__mul__() special method in the string class:
str.__mul__(), is implemented and returns the string repeated five times.
Let's look at the version with the integer in the first (left) position:
5 * "5". In this case, the integer class's
__mul__() method is called:
__mul__() method in the integer class returns
NotImplemented when a string is passed as an argument.
The interpreter now falls back to the reflected multiplication method in the right operand's class. That's the string in this case:
The reflected version of the multiplication special method is similar to the standard version:
5 * "5" returns the expected value not because the
__mul__() method in the integer class deals with this scenario. Instead, it's the string's reflected multiplication special method,
__rmul__(), that takes care of this case.
A Final Example With A User-Defined Class
Let's see whether this approach can also work with user-defined classes. You can create a simple class called
SubstackArticle with only one data attribute,
.number_of_words. I'm keeping this class very simple for demonstration purposes:
You define the class and create an instance of this class called
my_article. It doesn't matter in which order you try to multiply
my_article and an integer. Neither option works. The code raises a
TypeError in both scenarios.
Next, you can define this class's
__mul__() dunder method. This tells the interpreter how to deal with multiplication when an instance of
SubstackArticle is the first operand. I'm also defining the class's
__repr__() special method so that you can identify the object when you print it out (read more about
__repr__() in this Real Python article I wrote recently):
__mul__() special method checks whether
other is an integer. If it is, the method returns a new
SubstackArticle instance with the word count multiplied by the value of
other. We'll return to this method later to deal with the case when
other is not an integer.
__mul__() is defined,
my_article * 3 is valid. It returns a new article with
3000 words since the original article had
3 * my_article still doesn't work because the
__mul__() method in the integer class cannot deal with a
SubstackArticle object. If you print
NotImplemented is returned.
So, let's also define the reflected version of the multiplication special method in
SubstackArticle has the
__rmul__() method defined, when
3 .__mul__(my_article) returns
my_article.__rmul__(3) is called.
__rmul__() to behave in the same way as
__mul__(). Therefore, both multiplications return a
SubstackArticle object with
3000 words. By defining both
__rmul__(), you enabled the multiplication of a
SubstackArticle with an integer to be commutative—it doesn't matter which comes first in the multiplication.
Let's add one more temporary line to ensure we can follow when
__rmul__() is called. Note the extra
print() function call in
This version confirms that
__rmul__() is called when the first (or left) operand is an integer since
3 .__mul__(my_article) returns
Can you also multiply a
SubstackArticle by other types?
Let's try to multiply the
SubstackArticle instance by a string:
This doesn't work. However, this behaviour is not desirable. Both multiplications with strings returned
None. It shouldn't be surprising that both returned the same value since you defined both
__rmul__(). However, you want this to raise an error and not return
__mul__() special method checks whether
other is an integer. However, when an object of a different data type is used as the second operand in a multiplication, the method returns
None. Recall that functions will always implicitly return
None if there's no
You can fix this by returning
NotImplemented for all scenarios other than integers:
Now, the code raises a
TypeError when you attempt to multiply a
SubstackArticle instance with a string or any other data type that is not an integer.
As is often the case, you'll find more detail waiting to be discovered when you dig a bit deeper beneath the surface of any Python topic. In this article, you explored a few examples of what happens when you add or multiply objects of different types.
The reflected operand version of
__radd__() and the reflected operand version of
__rmul__(). I'm sure you've cracked the code behind the naming of these reflected operand versions! Most binary arithmetic operations can have a method to deal with reflected operands. You can see the full list here.
Next time you define some of these special methods in a class, you may want to consider whether you also need the reflected operand version.
Code in this article uses Python 3.11
It was hard to decide what the first article on The Python Coding Stack should be. Then I just stopping thinking about it and published this one. If you're new to my articles, you'll soon see that the type of articles I write can be varied. Some focus on specific corners of Python programming, such as this one. Others will be broader projects. And sometimes there will be higher-level analogies and discussion.
You may be wondering what's Stop Stack? It's a quick way for me to communicate with you. At the end of each article I'll include a few short bullet points that are unrelated to the main article.
I'm looking forward to running the first cohort of The Python Coding Programme later this month. Live sessions with very small cohorts over 3 weeks, with 90 minutes live on Zoom every day (4 days a week). Each cohort only has 4 participants and there's active mentoring throughout with a private forum for the cohort to continue discussions. These cohorts are for those at the start of their programming journey. If you know anyone interested, here's some more information about The Python Coding Programme for Beginners.
Coming soon on The Python Coding Stack: A series of articles about classes and object-oriented programming which build on the Hogwarts School of Codecraft and Algorithmancy Twitter series which some of you may have seen.
Have you tried using Substack's new Notes? It's a bit like Twitter, yes, but it feels as though the interactions will be different and that Notes will have its own vibes. I'll be there as I try out this new platform. Hope to chat with you there.
Substack’s code blocks are not ideal at the moment. Hopefully they’ll improve soon. The best compromise I’ve found is to include the code in properly formatted and syntax-highlighted image snippets instead of Substack’s native code blocks. All images with code have the text of the code available in the image ALT text. The longer code segments also have a link (as a caption) to a GitHub Gist to make copying the code easier. Let me know your thoughts…
I applaud, once again, your efforts concerning making code both pleasing to look at and easy to access as text.
One question: how does one access the alt text of one of your shorter snippets? I can see the alt text, in a tool-tip sort of window, when I hover over the image, but I don't see a way to copy it. (Unlike, say, Twitter's pop-up that comes when clicking the "Alt" button.) Yes, I can get to it by doing View Source and searching for "alt=", but I presume you have something less clunky in mind.
Not that I need to do this, right here and now, but I thought it might be worth asking, as a general matter.
If the answer is, "There is no good way; that's why I use the 'copy code' links to GitHub, and anything that's too short to merit a link should be NBD for you to type in yourself", that's perfectly fine!
I do hope Substack does something about improving the style of embedded code. That would be big.
Nice explanation on the r methods with good illustrative case.