Imagine that you are developing software for a large shipping company (why not imagine a small company). And you have the task of creating a function for calculating fees for ships based on their cargo weight. Easy peasy:

``````WEIGHT_RATES = [
( 10, 10.55),
(  5, 5.05),
(  2, 3.35),
(  0, 1.25)
]

def calculate_fees(weight):
if weight < 0:
raise ValueError("Can't calculate shipping charge of negative weights")
for min_weight, rate in WEIGHT_RATES:
if weight > min_weight:
return weight * rate
``````

Simple enough.

But one day your program will eventually work in another country, say, the United States. There's one problem — you have to use pounds instead of kilograms to calculate fees. No problem, there you are:

``````def calculate_fees(weight, pnds):
if pnds:
weight /= 2.2
if weight < 0:
raise ValueError("Can't calculate shipping charge of negative weights")
for min_weight, rate in WEIGHT_RATES:
if weight > min_weight:
return weight * rate

``````

This becomes more and more complicated, but then there is another requirement — you have to raise the exception if the weight exceeds 1000 kilos for specific directions:

``````def calculate_fees(weight, pnds, exceed):
if pnds:
weight /= 2.2
if exceed and weight > 1000:
raise Exception("Weight can't exceed 1000 kg")
if weight < 0:
raise ValueError("Can't calculate shipping charge of negative weights")
for min_weight, rate in WEIGHT_RATES:
if weight > min_weight:
return weight * rate
``````

Do you see the problem? In this dummy example, you get to a function with 3 position arguments, the last two of which have the same type. The end-user or you as a developer can easily forget which one should come first and mess them up. Thanks to the same type, the Python program will not fail and you will get a logical error:

``````calculate_fees(2000, True, False)
``````

or

``````calculate_fees(2000, False, True)
``````

You can use keyword arguments with default values and it's a good practice:

``````def calculate_fees(weight, pnds=False, exceed=False):
if pnds:
weight /= 2.2
if exceed and weight > 1000:
raise Exception("Weight can't exceed 1000 kg")
if weight < 0:
raise ValueError("Can't calculate shipping charge of negative weights")
for min_weight, rate in WEIGHT_RATES:
if weight > min_weight:
return weight * rate
``````

But the problem is not solved. To solve the problem, you need to add one asterisk at the beginning of the keyword arguments list:

``````def calculate_fees(weight, *, pnds=False, exceed=False):
if pnds:
weight /= 2.2
if exceed and weight > 1000:
raise Exception("Weight can't exceed 1000 kg")
if weight < 0:
raise ValueError("Can't calculate shipping charge of negative weights")
for min_weight, rate in WEIGHT_RATES:
if weight > min_weight:
return weight * rate
``````

That's it, next time you will call this function you will get an error:

``````>>>calculate_fees(2000, True, False)
TypeError: calculate_fees() takes 1 positional argument but 3 were given
``````

More info: PEP-3102

Last updated Thu Dec 06 2018