Let’s go through the whole OILER process with another bug. We’ll use a slightly modified version of the code, which is exhibiting a different symptom.
Code
import csvdef read_two_column_csv(file_name):withopen(file_name, "r") asfile: reader = csv.reader(file)next(reader) # skip header row data = {}for item, value in reader: data[item] = valuereturn datadef create_combined_inventory(quantities, prices): combined_data = {}for item in quantities: combined_data[item] = {"quantity": quantities[item], "price": 0}for item in prices: price = prices[item]if item in combined_data: combined_data[item]["price"] = priceelse: combined_data[item] = {"price": price}return combined_data## Process a shopping requestdef check_one_product(inventory, product, request_qty): message ="" available_qty = inventory[product]["quantity"] price = inventory[product]["price"]if request_qty > available_qty: qty = available_qty apology ="Sorry, we only have "else: qty = request_qty apology ="" subtotal = qty * price message = (f"${qty * price:.2f}: {apology}"f"{qty}{'unit'if qty ==1else'units'} of {product}; "f"${price:.2f} per unit.\n" )return subtotal, messagedef process_shopping_request(shopping_request, inventory): response ="" total =0for product, request_qty in shopping_request.items(): subtotal, message = check_one_product(inventory, product, request_qty) total += subtotal response += message response +=f"----\n${total:.2f} Total\n"return responsequantities = read_two_column_csv("data/quantities.csv")prices = read_two_column_csv("data/prices.csv")inventory = create_combined_inventory(quantities, prices)print(process_shopping_request({"apples": 500, "bananas": 3, "pears": 1}, inventory))print( process_shopping_request( {"apples": 500, "bananas": 3, "oranges": 4, "pears": 1}, inventory ))
---------------------------------------------------------------------------TypeError Traceback (most recent call last)
Cell In[1], line 63 60 prices = read_two_column_csv("data/prices.csv")
62 inventory = create_combined_inventory(quantities, prices)
---> 63print(process_shopping_request({"apples":500,"bananas":3,"pears":1},inventory))
64print(
65 process_shopping_request(
66 {"apples": 500, "bananas": 3, "oranges": 4, "pears": 1}, inventory
67 )
68 )
Cell In[1], line 52, in process_shopping_request(shopping_request, inventory) 50 total =0 51for product, request_qty in shopping_request.items():
---> 52 subtotal, message =check_one_product(inventory,product,request_qty) 53 total += subtotal
54 response += message
Cell In[1], line 33, in check_one_product(inventory, product, request_qty) 30 available_qty = inventory[product]["quantity"]
31 price = inventory[product]["price"]
---> 33ifrequest_qty>available_qty:
34 qty = available_qty
35 apology ="Sorry, we only have "TypeError: '>' not supported between instances of 'int' and 'str'
8.1 Orient
The code is extremely similar to the original running example, and everything from the Orient step applies here as well. As a reminder, here is the abstract execution diagram that we developed to orient ourselves.
An execution diagram for our shopping code.
8.2 Investigate
8.2.1 Describe the Symptom
Again, the program has not run to completion, and we have an error message. This time, the error message is different. It says TypeError: '>' not supported between instances of 'int' and 'str'.
8.2.2 Identify the Proximate Cause
8.2.2.1 Find the Location
Reading the stack trace error message from the bottom up, we see that the proximate location is line 35, which is the line that contains the comparison if request_qty > available_qty:.
8.2.2.2 Hypothesize a Cause
The error message is telling us that we are trying to compare an integer and a string using the > operator. So the expression to the left of the > operator, request_qty, must be an integer, and the expression to the right, available_qty, must be a string.
We can confirm this by invoking print(), as in the following. We then raise an Exception to stop the program from running further.
Code
import csvdef read_two_column_csv(file_name):withopen(file_name, "r") asfile: reader = csv.reader(file)next(reader) # skip header row data = {}for item, value in reader: data[item] = valuereturn datadef create_combined_inventory(quantities, prices): combined_data = {}for item in quantities: combined_data[item] = {"quantity": quantities[item], "price": 0}for item in prices: price = prices[item]if item in combined_data: combined_data[item]["price"] = priceelse: combined_data[item] = {"price": price}return combined_data## Process a shopping requestdef check_one_product(inventory, product, request_qty): message ="" available_qty = inventory[product]["quantity"] price = inventory[product]["price"]print(f"type of request_qty: {type(request_qty)}")print(f"type of available_qty: {type(available_qty)}")raiseException("Stopping execution of this cell")if request_qty > available_qty: qty = available_qty apology ="Sorry, we only have "else: qty = request_qty apology ="" subtotal = qty * price message = (f"${qty * price:.2f}: {apology}"f"{qty}{'unit'if qty ==1else'units'} of {product}; "f"${price:.2f} per unit.\n" )return subtotal, messagedef process_shopping_request(shopping_request, inventory): response ="" total =0for product, request_qty in shopping_request.items(): subtotal, message = check_one_product(inventory, product, request_qty) total += subtotal response += message response +=f"----\n${total:.2f} Total\n"return responsequantities = read_two_column_csv("data/quantities.csv")prices = read_two_column_csv("data/prices.csv")inventory = create_combined_inventory(quantities, prices)print(process_shopping_request({"apples": 500, "bananas": 3, "pears": 1}, inventory))print( process_shopping_request( {"apples": 500, "bananas": 3, "oranges": 4, "pears": 1}, inventory ))
type of request_qty: <class 'int'>
type of available_qty: <class 'str'>
---------------------------------------------------------------------------Exception Traceback (most recent call last)
Cell In[2], line 66 63 prices = read_two_column_csv("data/prices.csv")
65 inventory = create_combined_inventory(quantities, prices)
---> 66print(process_shopping_request({"apples":500,"bananas":3,"pears":1},inventory))
67print(
68 process_shopping_request(
69 {"apples": 500, "bananas": 3, "oranges": 4, "pears": 1}, inventory
70 )
71 )
Cell In[2], line 55, in process_shopping_request(shopping_request, inventory) 53 total =0 54for product, request_qty in shopping_request.items():
---> 55 subtotal, message =check_one_product(inventory,product,request_qty) 56 total += subtotal
57 response += message
Cell In[2], line 35, in check_one_product(inventory, product, request_qty) 33print(f"type of request_qty: {type(request_qty)}")
34print(f"type of available_qty: {type(available_qty)}")
---> 35raiseException("Stopping execution of this cell")
36if request_qty > available_qty:
37 qty = available_qty
Exception: Stopping execution of this cell
8.2.2.2.1 Code vs. Intention Mismatch
Is it a code vs. intention mismatch? The intention is to compare the quantities of the requested item and the available item. The code is trying to do that. So, there is no mismatch here.
8.2.2.2.2 Computation Misunderstanding
Did we misunderstand the > operator? We thought it compared two numbers and evaluates to True if the first is larger than the second. Let’s do a quick check of our understanding.
Code
x =5x >3
True
Code
x >7
False
It doesn’t seem like we misunderstod the > operator, so let’s keep moving.
8.2.2.2.3 Data Fault
Did we expect available_qty to be a string? No, we expected it to be an integer. So, this is a data fault.
8.3 Locate the Root Cause
We could do this with either backward tracing or forward tracing. Let’s do forward tracing this time, since we did backward tracing last time and it turned out that forward tracing would have been easier.
After we read in the files, we would expect the quantities to be integers. So, let’s call print() to check whether they are.
Code
import csvdef read_two_column_csv(file_name):withopen(file_name, "r") asfile: reader = csv.reader(file)next(reader) # skip header row data = {}for item, value in reader: data[item] = valuereturn datadef create_combined_inventory(quantities, prices): combined_data = {}for item in quantities: combined_data[item] = {"quantity": quantities[item], "price": 0}for item in prices: price = prices[item]if item in combined_data: combined_data[item]["price"] = priceelse: combined_data[item] = {"price": price}return combined_data## Process a shopping requestdef check_one_product(inventory, product, request_qty): message ="" available_qty = inventory[product]["quantity"] price = inventory[product]["price"]if request_qty > available_qty: qty = available_qty apology ="Sorry, we only have "else: qty = request_qty apology ="" subtotal = qty * price message = (f"${qty * price:.2f}: {apology}"f"{qty}{'unit'if qty ==1else'units'} of {product}; "f"${price:.2f} per unit.\n" )return subtotal, messagedef process_shopping_request(shopping_request, inventory): response ="" total =0for product, request_qty in shopping_request.items(): subtotal, message = check_one_product(inventory, product, request_qty) total += subtotal response += message response +=f"----\n${total:.2f} Total\n"return responsequantities = read_two_column_csv("data/quantities.csv")prices = read_two_column_csv("data/prices.csv")print(f"type of quantities['apples']: {type(quantities['apples'])}")# inventory = create_combined_inventory(quantities, prices)# print(process_shopping_request({"apples": 500, "bananas": 3, "pears": 1}, inventory))# print(process_shopping_request({"apples": 500, "bananas": 3, "oranges": 4, "pears": 1}, inventory))
type of quantities['apples']: <class 'str'>
Hmm. It’s a string. So, the root cause is that we misunderstood the computation defined in read_two_column_csv(). It is reading the values as strings instead of as numbers.
8.3.0.1 Experiment with Fixes
We will need to convert those strings into numbers. We can do this by using the int() function.
But when should we do that?
We could alter the read_two_column_csv(), to make it match our expectations of what that function was supposed to do. But we need to be careful. That function is also used to read in prices, and could be used to read in any two-column CSV file.
So, we could add an optional parameter that specifies that we want an integer conversion.
Code
import csvdef read_two_column_csv(file_name, convert_to_integer=False):withopen(file_name, "r") asfile: reader = csv.reader(file)next(reader) # skip header row data = {}for item, value in reader:if convert_to_integer: value =int(value) data[item] = valuereturn datadef create_combined_inventory(quantities, prices): combined_data = {}for item in quantities: combined_data[item] = {"quantity": quantities[item], "price": 0}for item in prices: price = prices[item]if item in combined_data: combined_data[item]["price"] = priceelse: combined_data[item] = {"price": price}return combined_data## Process a shopping requestdef check_one_product(inventory, product, request_qty): message ="" available_qty = inventory[product]["quantity"] price = inventory[product]["price"]if request_qty > available_qty: qty = available_qty apology ="Sorry, we only have "else: qty = request_qty apology ="" subtotal = qty * price message = (f"${qty * price:.2f}: {apology}"f"{qty}{'unit'if qty ==1else'units'} of {product}; "f"${price:.2f} per unit.\n" )return subtotal, messagedef process_shopping_request(shopping_request, inventory): response ="" total =0for product, request_qty in shopping_request.items(): subtotal, message = check_one_product(inventory, product, request_qty) total += subtotal response += message response +=f"----\n${total:.2f} Total\n"return responsequantities = read_two_column_csv("data/quantities.csv", convert_to_integer=True)prices = read_two_column_csv("data/prices.csv")print(f"type of quantities['apples']: {type(quantities['apples'])}")inventory = create_combined_inventory(quantities, prices)print(process_shopping_request({"apples": 500, "bananas": 3, "pears": 1}, inventory))print( process_shopping_request( {"apples": 500, "bananas": 3, "oranges": 4, "pears": 1}, inventory ))
type of quantities['apples']: <class 'int'>
---------------------------------------------------------------------------ValueError Traceback (most recent call last)
Cell In[6], line 68 65print(f"type of quantities['apples']: {type(quantities['apples'])}")
67 inventory = create_combined_inventory(quantities, prices)
---> 68print(process_shopping_request({"apples":500,"bananas":3,"pears":1},inventory))
69print(
70 process_shopping_request(
71 {"apples": 500, "bananas": 3, "oranges": 4, "pears": 1}, inventory
72 )
73 )
Cell In[6], line 56, in process_shopping_request(shopping_request, inventory) 54 total =0 55for product, request_qty in shopping_request.items():
---> 56 subtotal, message =check_one_product(inventory,product,request_qty) 57 total += subtotal
58 response += message
Cell In[6], line 45, in check_one_product(inventory, product, request_qty) 42 apology ="" 43 subtotal = qty * price
44 message = (
---> 45f"${qty*price:.2f}: {apology}" 46f"{qty}{'unit'ifqty==1else'units'} of {product}; " 47f"${price:.2f} per unit.\n" 48 )
49return subtotal, message
ValueError: Unknown format code 'f' for object of type 'str'
Well, that conversion successfully made it so that quantities['apples'] is an integer. But eliminating that TypeError has triggered a ValueError somewhere later in the exeuction. We will have to go through the OILER process for that once we are done with the process for the TypeError we’ve been working on.
Another option for a fix is to convert the strings to numbers as part of the Combine step. That’s what the original code in the running example did.
Code
import csvdef read_two_column_csv(file_name):withopen(file_name, "r") asfile: reader = csv.reader(file)next(reader) # skip header row data = {}for item, value in reader: data[item] = valuereturn datadef create_combined_inventory(quantities, prices): combined_data = {}for item in quantities: combined_data[item] = {"quantity": int(quantities[item])}for item in prices: price = prices[item]if item in combined_data: combined_data[item]["price"] = priceelse: combined_data[item] = {"price": price}return combined_data## Process a shopping requestdef check_one_product(inventory, product, request_qty): message ="" available_qty = inventory[product]["quantity"]if"price"in inventory[product]: price = inventory[product]["price"]else: price =0if request_qty > available_qty: qty = available_qty apology ="Sorry, we only have "else: qty = request_qty apology ="" subtotal = qty * price message = (f"${qty * price:.2f}: {apology}"f"{qty}{'unit'if qty ==1else'units'} of {product}; "f"${price:.2f} per unit.\n" )return subtotal, messagedef process_shopping_request(shopping_request, inventory): response ="" total =0for product, request_qty in shopping_request.items(): subtotal, message = check_one_product(inventory, product, request_qty) total += subtotal response += message response +=f"----\n${total:.2f} Total\n"return responsequantities = read_two_column_csv("data/quantities.csv")prices = read_two_column_csv("data/prices.csv")inventory = create_combined_inventory(quantities, prices)print(process_shopping_request({"apples": 500, "bananas": 3, "pears": 1}, inventory))print( process_shopping_request( {"apples": 500, "bananas": 3, "oranges": 4, "pears": 1}, inventory ))
That also resolved the initial bug, but triggers the same follow-on error.
We could even move the conversion to the shopping part of the code, where we are actually using the quantities.
See line 35 below. This would be a little less efficient, since we would be converting the strings to numbers every time we use them. And if we use them somewhere else in the code, we would have to do the conversion there as well. Thus, it would be better to do the conversion in the Combine step or in the read_two_column_csv function.
Code
import csvdef read_two_column_csv(file_name):withopen(file_name, "r") asfile: reader = csv.reader(file)next(reader) # skip header row data = {}for item, value in reader: data[item] = valuereturn datadef create_combined_inventory(quantities, prices): combined_data = {}for item in quantities: combined_data[item] = {"quantity": quantities[item], "price": 0}for item in prices: price = prices[item]if item in combined_data: combined_data[item]["price"] = priceelse: combined_data[item] = {"price": price}return combined_data## Process a shopping requestdef check_one_product(inventory, product, request_qty): message ="" available_qty = inventory[product]["quantity"] price = inventory[product]["price"] available_qty =int(available_qty)if request_qty > available_qty: qty = available_qty apology ="Sorry, we only have "else: qty = request_qty apology ="" subtotal = qty * price message = (f"${qty * price:.2f}: {apology}"f"{qty}{'unit'if qty ==1else'units'} of {product}; "f"${price:.2f} per unit.\n" )return subtotal, messagedef process_shopping_request(shopping_request, inventory): response ="" total =0for product, request_qty in shopping_request.items(): subtotal, message = check_one_product(inventory, product, request_qty) total += subtotal response += message response +=f"----\n${total:.2f} Total\n"return responsequantities = read_two_column_csv("data/quantities.csv")prices = read_two_column_csv("data/prices.csv")inventory = create_combined_inventory(quantities, prices)print(process_shopping_request({"apples": 500, "bananas": 3, "pears": 1}, inventory))print( process_shopping_request( {"apples": 500, "bananas": 3, "oranges": 4, "pears": 1}, inventory ))
8.4 Reflect
8.4.1 Bug Log Entry
Cell In[1], line 35
32 available_qty = inventory[product]["quantity"]
33 price = inventory[product]["price"]
---> 35 if request_qty > available_qty:
36 qty = available_qty
37 apology = "Sorry, we only have "
TypeError: '>' not supported between instances of 'int' and 'str'
Field
Description
Symptom
TypeError: ‘>’ not supported between instances of ‘int’ and ‘str’
Proximal Cause
data fault: we expected the inventory quantities to be integers but they were strings
Root Cause
computation misunderstanding: in the Read step of the program it read the quantities as strings instead of as integers
Fix
Convert the string to integers, either in the Read step, the Combine step, or the Shop step
8.4.2 Bug Recipe
Generalizing, let’s write a recipe for diagnosing and fixing similar bugs in the future.
Bug Name
TypeError
Symptoms
Runtime error with message that mentions TypeError. The error message says operands that are not of compatible types. You have to figure out which variable names or expressions correspond to the types it mentions.
Potential Proximal Causes
code-intention mismatch: was that the operator you intended? the two operands?
computation misunderstanding: does the operator work the way you thought?
data fault: are the data values what you expected them to be?
Potential Root Causes
computation misunderstanding: code that is reading and setting the data is not converting it into the type you expected. In particular, remember that the csv module always reads in data as strings.
Potential Fixes
If the data is correct but just needs a data type conversion, decide the best place to do the conversion:
at the location where data is read in from a file or other external source.
somewhere during intermediate processing.
at the location of use. But this may be less desirable, as it requires redundant conversions if the data is used multiple times or in multiple places in the code.
8.5 Future Proof Your Code
8.5.1 Side Effects
There is a follow-on error that we haven’t really looked at. Is there any chance that converting the quantities from strings to integers could have caused that problem, or any other problem? Let’s have a quick look at the last part of that stack trace:
Cell In[28], line 45
42 apology = ""
43 subtotal = qty * price
44 message = (
---> 45 f"${qty * price:.2f}: {apology}"
46 f"{qty} {'unit' if qty == 1 else 'units'} of {product}; "
47 f"${price:.2f} per unit.\n"
48 )
49 return subtotal, message
ValueError: Unknown format code 'f' for object of type 'str'
In this error message, the f is referring to price:.2f, not the ff before the quotes that signals the start of a formatted string literal (f-string). This f is a format specifier that is used to format a number as a floating point number. So, it seems that the error message is telling us that the price variable is a string, not a number.
This is not likely to be a side effect of our turning the quantities into integers, so the fix we made is OK. But it is a clue about what the next bug is.
8.5.2 Identify Technical Debt
When we converted the quantities to integers, we might have noticed that we have a similar problem with the prices. They are also strings, not numbers. But we probably do want them to be numbers, in this case floating point numbers. The error message that we just analyzed suggests this as well.
We have identified some technical debt in the program. At some point, the fact that we are reading in prices as strings will cause a problem. And actually, that debt is already coming due, in the form of our next error.
So let’s make a fix for that now.
We could add another special-case flag in read_two_column_csv to handle converting the price strings to floating point numbers.
But let’s do something a little more elegant. Let’s make the optional parameter be a converter function that converts the second column string in any arbitrary way. This way, the read_two_column_csv function is even more general and reusable.
We will make the default converter function be the identity function, so that it doesn’t change the value at all. This way, we can use the function as we have been using it, without having to specify the converter function. But we can also use it to convert the prices to floating point numbers and the quantities to integers.
See lines 5, 11, and 61-66 below.
Code
import csvdef read_two_column_csv(file_name, second_col_converter=lambda x: x):withopen(file_name, "r") asfile: reader = csv.reader(file)next(reader) # skip header row data = {}for item, value in reader: data[item] = second_col_converter(value)return datadef create_combined_inventory(quantities, prices): combined_data = {}for item in quantities: combined_data[item] = {"quantity": quantities[item], "price": 0}for item in prices: price = prices[item]if item in combined_data: combined_data[item]["price"] = priceelse: combined_data[item] = {"price": price}return combined_data## Process a shopping requestdef check_one_product(inventory, product, request_qty): message ="" available_qty = inventory[product]["quantity"] price = inventory[product]["price"]print(f"type of price: {type(price)}")if request_qty > available_qty: qty = available_qty apology ="Sorry, we only have "else: qty = request_qty apology ="" subtotal = qty * price message = (f"${qty * price:.2f}: {apology}"f"{qty}{'unit'if qty ==1else'units'} of {product}; "f"${price:.2f} per unit.\n" )return subtotal, messagedef process_shopping_request(shopping_request, inventory): response ="" total =0for product, request_qty in shopping_request.items(): subtotal, message = check_one_product(inventory, product, request_qty) total += subtotal response += message response +=f"----\n${total:.2f} Total\n"return responsequantities = read_two_column_csv("data/quantities.csv", int) # int acts as a built-in function, converting a string to an integerprices = read_two_column_csv("data/prices.csv", lambda x: float(x[1:])) # remove the dollar sign and convert to a floatprint(f"types of quantities and prices: {type(quantities["apples"])}, {type(prices["apples"])}")inventory = create_combined_inventory(quantities, prices)print(process_shopping_request({"apples": 500, "bananas": 3, "pears": 1}, inventory))print( process_shopping_request( {"apples": 500, "bananas": 3, "oranges": 4, "pears": 1}, inventory ))
types of quantities and prices: <class 'int'>, <class 'float'>
type of price: <class 'float'>
type of price: <class 'float'>
type of price: <class 'float'>
$10.00: Sorry, we only have 10 units of apples; $1.00 per unit.
$6.00: 3 units of bananas; $2.00 per unit.
$1.50: 1 unit of pears; $1.50 per unit.
----
$17.50 Total
type of price: <class 'float'>
type of price: <class 'float'>
type of price: <class 'int'>
type of price: <class 'float'>
$10.00: Sorry, we only have 10 units of apples; $1.00 per unit.
$6.00: 3 units of bananas; $2.00 per unit.
$0.00: 4 units of oranges; $0.00 per unit.
$1.50: 1 unit of pears; $1.50 per unit.
----
$17.50 Total
Paying down this technical debt has yielded a nice bonus. We got rid of the next bug as well.
8.5.3 Documentation
We could make sure future programmers understand what the combine function is doing by adding type hints and a docstring. This will primarily help someone in the Orient stage of debugging, but it could also help in identifying a data fault or a computation misunderstanding.
Code
import csvdef read_two_column_csv(file_name, second_col_converter=lambda x: x):withopen(file_name, "r") asfile: reader = csv.reader(file)next(reader) # skip header row data = {}for item, value in reader: data[item] = second_col_converter(value)return datadef create_combined_inventory( quantities: dict[str, int], prices: dict[str, float]) ->dict[str, dict]:""" Create a combined inventory dictionary from quantities and prices. Parameters: quantities (dict[str, int]): A dictionary where keys are item names and values are quantities. prices (dict[str, float]): A dictionary where keys are item names and values are prices. Returns: dict[str, dict]: A dictionary where keys are item names and values are dictionaries containing 'quantity' and 'price' keys. """ combined_data = {}for item in quantities: combined_data[item] = {"quantity": quantities[item], "price": 0}for item in prices: price = prices[item]if item in combined_data: combined_data[item]["price"] = priceelse: combined_data[item] = {"price": price}return combined_data## Process a shopping requestdef check_one_product(inventory, product, request_qty): message ="" available_qty = inventory[product]["quantity"] price = inventory[product]["price"]print(f"type of price: {type(price)}")if request_qty > available_qty: qty = available_qty apology ="Sorry, we only have "else: qty = request_qty apology ="" subtotal = qty * price message = (f"${qty * price:.2f}: {apology}"f"{qty}{'unit'if qty ==1else'units'} of {product}; "f"${price:.2f} per unit.\n" )return subtotal, messagedef process_shopping_request(shopping_request, inventory): response ="" total =0for product, request_qty in shopping_request.items(): subtotal, message = check_one_product(inventory, product, request_qty) total += subtotal response += message response +=f"----\n${total:.2f} Total\n"return responsequantities = read_two_column_csv("data/quantities.csv", int) # int acts as a built-in function, converting a string to an integerprices = read_two_column_csv("data/prices.csv", lambda x: float(x[1:])) # remove the dollar sign and convert to a floatprint(f"types of quantities and prices: {type(quantities["apples"])}, {type(prices["apples"])}")inventory = create_combined_inventory(quantities, prices)print(process_shopping_request({"apples": 500, "bananas": 3, "pears": 1}, inventory))print( process_shopping_request( {"apples": 500, "bananas": 3, "oranges": 4, "pears": 1}, inventory ))
types of quantities and prices: <class 'int'>, <class 'float'>
type of price: <class 'float'>
type of price: <class 'float'>
type of price: <class 'float'>
$10.00: Sorry, we only have 10 units of apples; $1.00 per unit.
$6.00: 3 units of bananas; $2.00 per unit.
$1.50: 1 unit of pears; $1.50 per unit.
----
$17.50 Total
type of price: <class 'float'>
type of price: <class 'float'>
type of price: <class 'int'>
type of price: <class 'float'>
$10.00: Sorry, we only have 10 units of apples; $1.00 per unit.
$6.00: 3 units of bananas; $2.00 per unit.
$0.00: 4 units of oranges; $0.00 per unit.
$1.50: 1 unit of pears; $1.50 per unit.
----
$17.50 Total