Name the result
Prefer calculate_invoice_total over process_data. A concrete verb and noun tell the next reader what the function promises.
Pass inputs explicitly
Hidden global state makes a function harder to reuse and test. Pass the values it needs and return the result.
Keep one level of responsibility
Calculating a total and sending an email are different jobs. Separate pure calculation from external effects such as files, networks, and databases.
Validate at the boundary
Reject impossible values close to where they enter the system. A ValueError with a precise message is better than a mysterious result later.
Working example
from decimal import Decimal
def calculate_invoice_total(
subtotal: Decimal,
tax_rate: Decimal,
discount: Decimal = Decimal("0"),
) -> Decimal:
if subtotal < 0:
raise ValueError("subtotal cannot be negative")
if not Decimal("0") <= tax_rate <= Decimal("1"):
raise ValueError("tax_rate must be between 0 and 1")
if not Decimal("0") <= discount <= subtotal:
raise ValueError("discount must be between 0 and subtotal")
taxable = subtotal - discount
return (taxable * (Decimal("1") + tax_rate)).quantize(Decimal("0.01"))