Price an order from its lines
An order total is a chain of small rules: line amounts, discounts, a coupon, shipping. One function per rule keeps each rule readable and testable on its own.
- Split a pricing job into one function per rule, each returning a number
- Apply rules in the right order: line discounts, then the coupon, then shipping
- Reuse the pricing functions to compare several orders
A checkout page answers one question, *what does this order cost?*, but the answer depends on several rules applied in order. Written as one long block, those rules tangle together; written as small functions, each one can be read, tested and changed alone.
This lesson adds nothing new to the language. It uses lists of dictionaries (see Dictionaries & Sets), functions with defaults and return values (see Functions) and f-strings (see Strings & Text) together, in the order a real checkout applies them.
Run each example to see what it prints. Running examples doesn't count toward progress.
def line_amount(line): """Return quantity times price for one order line.""" return line["quantity"] * line["price"] def shipping(subtotal, free_from=50): """Return the shipping fee for a subtotal.""" if subtotal >= free_from: return 0 return 5.99 def order_total(lines): """Return the subtotal of all lines plus shipping.""" subtotal = 0 for line in lines: subtotal = subtotal + line_amount(line) return subtotal + shipping(subtotal) lines = [ {"sku": "HOM-22847", "quantity": 2, "price": 15.5}, {"sku": "SPO-11681", "quantity": 1, "price": 7.49}, ] for line in lines: print(f"{line['sku']} {line_amount(line):>6.2f}") print(f"total {order_total(lines):>6.2f}")
Once orders are priced by a function, questions about many orders are just loops that call it. Here the same order_total ranks a day's orders and flags the ones that will need a signature on delivery. The order ID and the lines live together in a dictionary, the shape an online shop's API would send.
def line_amount(line): return line["quantity"] * line["price"] def order_total(lines, free_from=50): subtotal = 0 for line in lines: subtotal = subtotal + line_amount(line) if subtotal >= free_from: return subtotal return subtotal + 5.99 orders = [ {"order_id": "O100043", "lines": [{"quantity": 1, "price": 6.49}]}, {"order_id": "O104839", "lines": [{"quantity": 1, "price": 51.49}, {"quantity": 2, "price": 15.99}]}, {"order_id": "O101217", "lines": [{"quantity": 3, "price": 12.0}]}, ] biggest = None for order in orders: total = order_total(order["lines"]) flag = " signature" if total >= 80 else "" print(f"{order['order_id']} {total:>7.2f}{flag}") if biggest is None or total > biggest[1]: biggest = (order["order_id"], total) print("largest:", biggest[0])
When to use it: any calculation with several rules. Give each rule a function with a name that says what it decides, pass in what it needs, and return a number. Mistakes to avoid:
- Applying rules in the wrong order: a coupon usually comes off before the free-shipping check, not after.
- Printing inside the pricing functions, which makes them useless for comparisons and totals.
- Repeating a rule's arithmetic in two places instead of calling its function.
- Price an order with one function per rule: line amount, discounts, coupon, shipping.
- Apply the rules in the order the business applies them, and return numbers rather than printing them.
- Questions about many orders are loops that call the same functions.
- Next: summarise a list of records, the films in Runtime Films' catalogue.