<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 On the Importance of Errors (oh, and Operator Overloading)
You may remember seeing the following code in the ‘Philosophy’ lectures we were talking about this function
Step2:  I argued that one of the main drawbacks of this code is that "[it] implicitly assumes the user knows to pass in numbers". In this lecture I aim to do two things. Firstly, I want you to understand what the problem is from a technical point of view. Secondly, I want you to grasp the importance of what is potentially at stake.
Step4:  In the documentation above we mention clearly what our function does and how it is to be used. Take particular note to the part where we say mass and acceleration are supposed to be floats or integers. Telling our end-user about how we expect our code to be used helps them avoid bugs and other software defects that could result from improper use. BUT in Python (unlike some other statically-typed languages) the 'python way' is to allow the user to do whatever they want; we just trust that they will be responsible.
In the documentation above we mention clearly what our function does and how it is to be used. Take particular note to the part where we say ‘mass’ and ‘acceleration’ are supposed to be floats or integers. Telling our end-user about how we expect our code to be used helps them avoid bugs and other software defects that could result from improper use. But with that said, if somebody wants to misuse your code they should have the freedom to do just that…
Freedom & Responsibility
In Python there is a saying
Step5:  'add' and 'add2' are very similar functions, the main difference is that 'add' has a docstring that explains what the function does and also explains what the expected input is. 'add2' doesn't have documentation and it 'forces' the user to pass in integers. We haven't seen the assert statement yet, but basically what it does is it tells Python to throw an error whenever the condition is not met. In this case, if we give this function a string it will not work.
Step6:  Of these two functions 'add' is more Pythonic, the code is more flexible and it trusts the user to be responsible. Meanwhile the function 'add2' is much more rigid and demands to be used in a certain way. Overall you should try to write code more like the 'add' function as opposed to 'add2'.
Moreover since the 'add' function doesn't hard-code requirements this code can be used in unexpected ways...
Operator Overloading...
Step8:  The salient point is to notice is that the ‘+’ operator works not just on integers, but also tuples, lists, strings, class objects and so on. This is what we call 'operator overloading'.
Operator overloading occurs when a single operator has several different meaning/uses, depending on context. For numbers, ‘+’ means addition, for strings it means concatenation, and for other objects (i.e. custom classes) it could mean literally whatever we want it to mean.
In many ways, this is a great feature since it means the language ends up being a bit more concise and we have less symbols to remember. But you must be careful; There are bugs ahead!
Lets return to our function "force". Let’s call it with a bunch of objects (of different types) and see what happens!
Step9:  Notice that, just like the '+' operator '*' can mean different things in different contexts, thus we we give our net_force function strings, lists, and integers it behaves differently in each case.
Step10:  Because all of these uses are well-defined Python does not throw an error when you try to multiply an integer by a string; In each case the code "worked", and I'd argue that’s the problem!
Errors love you, love them back
"Errors should never pass silently". ~ Zen of Python
As beginners you might hate it when Python throws error messages at you; it feels like you got something wrong and now Python is punishing you for it. I’d counter that by saying this is very unhealthy attitude to have regarding error messages. I would argue that you should learn to love error messages, you should love them because they are the knights in shining armour that protect you from silly mistakes that could have disastrous consequences. 
Am I being overly-dramatic here? Well, consider the situation where you have medical data that you feed through a computer. It parses the data and the computer crashes on some error. What happens next? You reboot, fix, and re-run. Annoying sure, but no big deal.
Now imagine instead of getting an error Python just returns the wrong answer; think about the consequences of that just for a moment, wrong answers could easily have a disastrous impact on patient care.
Another reason you should prefer error messages instead of bad output is because an error in one place of code will probably cause an error much further on down the line (if you are lucky!). And the longer the error goes unnoticed the harder it is to find.
In the cell below I have a function called “Chemotherapy_radiation_dose”. This function is the same as our net_force function, the name change literally is just for dramatic effect.
Step11:  Okay, we have called the chemo function twice and we get the number 130 and the string 6565 returned to us. We shall use those values in a jiffy. Okay, now lets suppose that part of our program has a 'parse results' function. The task of this function is to get the results ready for printing.
Step12:  So in the above code I called parse_results with the number 130 and the function returns an error. Now, since are expecting to be given numbers as input and we want to return a string it makes sense to use str() to convert the number. Afterall, a program that spins errors when we give it correct input is of no use at all.
<ASSISTANT_TASK:>
Python Code:
def net_force(mass, acceleration):
    return mass * acceleration
Explanation: On the Importance of Errors (oh, and Operator Overloading)
You may remember seeing the following code in the ‘Philosophy’ lectures we were talking about this function:
End of explanation
# Function as above, but with DOCUMENTATION 
def net_force(mass, acceleration):
    
    Calculates f=ma, returns force.
    We assume mass, acceleration are of type int/float.
    
    return mass * acceleration
Explanation: I argued that one of the main drawbacks of this code is that "[it] implicitly assumes the user knows to pass in numbers". In this lecture I aim to do two things. Firstly, I want you to understand what the problem is from a technical point of view. Secondly, I want you to grasp the importance of what is potentially at stake.
End of explanation
def add(a, b):
    
    returns a + b, a and b should be integers
    
    return a + b
def add2(a, b):
    # btw, the "==" symbol is asking the question "is this equal to that". More on this later.
    assert a == type(int), "'{}' is not an integer".format(a)
    assert b == type(int), "'{}' is not an integer".format(b)
    return a + b
Explanation: In the documentation above we mention clearly what our function does and how it is to be used. Take particular note to the part where we say mass and acceleration are supposed to be floats or integers. Telling our end-user about how we expect our code to be used helps them avoid bugs and other software defects that could result from improper use. BUT in Python (unlike some other statically-typed languages) the 'python way' is to allow the user to do whatever they want; we just trust that they will be responsible.
In the documentation above we mention clearly what our function does and how it is to be used. Take particular note to the part where we say ‘mass’ and ‘acceleration’ are supposed to be floats or integers. Telling our end-user about how we expect our code to be used helps them avoid bugs and other software defects that could result from improper use. But with that said, if somebody wants to misuse your code they should have the freedom to do just that…
Freedom & Responsibility
In Python there is a saying: 
"We are all responsible users"
The meaning of that quote is basically "hey, if you use my code I trust that you will call my code how it is intended to be called. If my code breaks because you did something I didn't anticipate then that’s not my fault, its yours”.
And lets not forget that...
"...with great freedom comes great responsibility" ~ Eleanor Roosevelt
This idea of openness and responsibility is very much core to Python philosophy; the language allows you tinker with almost everything, and this incredible freedom is ultimately underpinned by a conventional wisdom that trusts you to be responsible. 
Think about it this way; if I write a piece of code that has a bunch of 'helpful warnings' in the documentation but it doesn't ‘hard-wire’ any checks or tests for input then that means other developers have have the ability to use the code in unexpected ways; maybe the behaviour you consider problematic or 'silly' is actually useful for some other developer out-there who wants to use your code for an entirely different purpose. 
In short, good Python code is very trusting of others; we should tell others how to use our code (normally in the documentation) but we shouldnt demand that they use our code as we expect them to. 
Let me try and explain with an example...
End of explanation
add2("hello", "world")
Explanation: 'add' and 'add2' are very similar functions, the main difference is that 'add' has a docstring that explains what the function does and also explains what the expected input is. 'add2' doesn't have documentation and it 'forces' the user to pass in integers. We haven't seen the assert statement yet, but basically what it does is it tells Python to throw an error whenever the condition is not met. In this case, if we give this function a string it will not work.
End of explanation
print( "✓"  +  "✓")    # strings (rememeber strings support *any* unicode character)
print(  1   +   2)      # integers
print( [1]  +  [2])     # lists
print( (1,) +  (2,))    # tuples      
Explanation: Of these two functions 'add' is more Pythonic, the code is more flexible and it trusts the user to be responsible. Meanwhile the function 'add2' is much more rigid and demands to be used in a certain way. Overall you should try to write code more like the 'add' function as opposed to 'add2'.
Moreover since the 'add' function doesn't hard-code requirements this code can be used in unexpected ways...
Operator Overloading...
End of explanation
def net_force(mass, acceleration):
     
    Returns mass * acceleration
    I trust you guys will use this function responsibly.
    
    return mass * acceleration
print(net_force(10, 10))
print(net_force("2", 10))
print(net_force([1], 10 ))
Explanation: The salient point is to notice is that the ‘+’ operator works not just on integers, but also tuples, lists, strings, class objects and so on. This is what we call 'operator overloading'.
Operator overloading occurs when a single operator has several different meaning/uses, depending on context. For numbers, ‘+’ means addition, for strings it means concatenation, and for other objects (i.e. custom classes) it could mean literally whatever we want it to mean.
In many ways, this is a great feature since it means the language ends up being a bit more concise and we have less symbols to remember. But you must be careful; There are bugs ahead!
Lets return to our function "force". Let’s call it with a bunch of objects (of different types) and see what happens!
End of explanation
# one more use of the '*' operator, this is called "unpacking"...
x = "12345"
print( *x, sep="_Batman_")
Explanation: Notice that, just like the '+' operator '*' can mean different things in different contexts, thus we we give our net_force function strings, lists, and integers it behaves differently in each case.
End of explanation
def chemotherapy_radiation_dose(patient_weight, cancer_stage):
    radiation_dose = patient_weight * cancer_stage
    return radiation_dose
print(chemotherapy_radiation_dose(65, 2))
print(chemotherapy_radiation_dose("65", 2))
Explanation: Because all of these uses are well-defined Python does not throw an error when you try to multiply an integer by a string; In each case the code "worked", and I'd argue that’s the problem!
Errors love you, love them back
"Errors should never pass silently". ~ Zen of Python
As beginners you might hate it when Python throws error messages at you; it feels like you got something wrong and now Python is punishing you for it. I’d counter that by saying this is very unhealthy attitude to have regarding error messages. I would argue that you should learn to love error messages, you should love them because they are the knights in shining armour that protect you from silly mistakes that could have disastrous consequences. 
Am I being overly-dramatic here? Well, consider the situation where you have medical data that you feed through a computer. It parses the data and the computer crashes on some error. What happens next? You reboot, fix, and re-run. Annoying sure, but no big deal.
Now imagine instead of getting an error Python just returns the wrong answer; think about the consequences of that just for a moment, wrong answers could easily have a disastrous impact on patient care.
Another reason you should prefer error messages instead of bad output is because an error in one place of code will probably cause an error much further on down the line (if you are lucky!). And the longer the error goes unnoticed the harder it is to find.
In the cell below I have a function called “Chemotherapy_radiation_dose”. This function is the same as our net_force function, the name change literally is just for dramatic effect.
End of explanation
def parse_results(number):
    return "The correct dose for the patient is " + number
parse_results(130)
Explanation: Okay, we have called the chemo function twice and we get the number 130 and the string 6565 returned to us. We shall use those values in a jiffy. Okay, now lets suppose that part of our program has a 'parse results' function. The task of this function is to get the results ready for printing.
End of explanation
def parse_results(number):
    return "The correct dose for the patient is " + str(number)
#                                                   ^ The change is right here
print(parse_results(130))
print(parse_results("6565"))
Explanation: So in the above code I called parse_results with the number 130 and the function returns an error. Now, since are expecting to be given numbers as input and we want to return a string it makes sense to use str() to convert the number. Afterall, a program that spins errors when we give it correct input is of no use at all.
End of explanation
<END_TASK>