Home / Optional arguments MUST use keywords (Python3)

Optional arguments MUST use keywords (Python3)

Optional arguments MUST use keywords (Python3)

Imagine that you are developing software for the big shipping company(why would you imagine small anyway). And you got a task to create a function for calculating a charge for ships based on their cargo weight. Easy breezy:

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

def shipping_charge(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 then one day your program eventually is going to work in another country, say the USA. One problem arises: we need to use pounds instead of kilograms for charging. No problem, here you are:

def shipping_charge(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 is getting complicated, but then one more requirement - you need to raise an exception if weight exceeds 1000 kilograms for specific directions:

def shipping_charge(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

You see the problem? On this stupid example, you came up to function with 3 positional arguments, two last of them with the same type. The end-user, or you as a developer can easily forget which one needs to come first and messed them up. Due to the same type Python program will not fail and you will get a logic error:

shipping_charge(2000, True, False)

or

shipping_charge(2000, False, True)


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

def shipping_charge(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 star in the argument list:

def shipping_charge(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 got an error:

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

More info:PEP-3102

 

Support author