Open for extension
An if/elif chain on a kind, such as if fmt == "csv": ... elif fmt == "json": ..., has to be edited for every new kind, and the same chain tends to appear in several places. The open/closed principle says code should be open to extension but closed to modification: adding a case should mean adding code, not editing code that works.
Put the varying behaviour behind one interface. Classes that share a method name do it, and so does a registry: a dict from name to function, filled by a decorator.
EXPORTERS = {}
def exporter(name):
def register(func):
EXPORTERS[name] = func
return func
return register
@exporter("csv")
def to_csv(rows):
lines = [",".join(row) for row in rows]
return "\n".join(lines)
rows = [["a", "b"], ["c", "d"]]
print(EXPORTERS["csv"](rows))A new format is now a new decorated function, possibly in another module, and nothing else changes.
Your task
Replace the chain with a registry: a dict DISCOUNTS, a decorator discount_rule(name) that stores a function in it and returns the function unchanged, one function per kind, and apply_discount(kind, total) that looks the rule up, raising ValueError for an unknown kind. Then add "fifth_off", which takes 20% off.
The explanation opens here once every check passes.