Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use default values and arbitrary arguments at one function call in Python?

I am learning Python.

I learned about default value and arbitrary arguments.

So, I made my function which has a parameter offering a default value, and another parameter having an arbitrary number of arguments.

def make_pizza(size=15,*toppings):
    """Summerize the pizza that we are about to make."""
    print("\nMaking a " + str(size) + 
    "-inch pizza with the following toppings: ")
    for topping in toppings:
        print("- " + topping)


make_pizza('onion','shrimp','goda cheese','mushroom' )
make_pizza(17,'ham','extra meat','sweet con','pepperoni')

At the first function call, I wanted to use the default value for the "size" parameter which is "15" and the "*toppings" arbitrary argument.

But, I couldn't figure out how to do this.

Could anyone tell me when I call a function with multiple arguments, how I can use parameters' default values and arbitrary arguments at one function call?

Sorry in advance if you feel uncomfortable with my English(I am not a native.)

like image 945
Yeni Avatar asked Jan 03 '23 09:01

Yeni


2 Answers

Mixing parameters with and without default values can indeed be confusing. Parameters are the names used in the function definition, arguments are the values passed into a call.

When calling, Python will always fill all parameters from positional arguments, including names with default values. size is just another parameter here, even though it has a default value. You can also use name=value syntax in a call to assign an argument value to a specific parameter (whether or not they have a default value). But you can't tell Python not to assign something to size, not with your current function definition, because everything before the *toppings parameter is always going to be a regular positional parameter.

The *toppings parameter will only capture any positional arguments after all the other parameters have received values. So 'onion' is assigned to size, and the remainder is assigned to *toppings.

In Python 3, you can make size a keyword-only parameter, by placing them as name=default after the *toppings name, or an empty *:

def make_pizza(*toppings, size=15):

Now size can only be set from a call with size=<new value> keyword argument syntax.

In Python 2, you can only capture such keyword arguments with a **kwargs catch-all parameter, after which you need to look into that dictionary for your size:

def make_pizza(*toppings, **kwargs):
    size = kwargs.get('size', 15)  # set a default if missing

In both cases, you have to remember to explicitly name size, and put such explicitly named keyword arguments after the positional arguments:

make_pizza('ham', 'extra meat', 'sweet con', 'pepperoni', size=17)
like image 159
Martijn Pieters Avatar answered Jan 05 '23 22:01

Martijn Pieters


There's a fundamental problem with your approach: It's ambiguous because how do you know if you intended it as size or as topping? Python can't do that so you need to find an alternative.

Well, you could simply remove size from the argument list and interpret the first *toppings argument as size if it's an integer:

def make_pizza(*toppings):
    if toppings and isinstance(toppings[0], int):
        size, toppings = toppings[0], toppings[1:]
    else:
        size = 15
    print("\nMaking a {}-inch pizza with the following toppings: ".format(size))
    for topping in toppings:
        print("- " + topping)

However that will fail for cases where a simple type check isn't possible. Maybe the better approach would be to make toppings a normal argument and size an optional argument:

def make_pizza(toppings, size=15):
    print("\nMaking a {}-inch pizza with the following toppings: ".format(size))
    for topping in toppings:
        print("- " + topping)

However then you need to pass in a sequence for toppings and it changes the order of toppings and size but it's probably a lot cleaner.

make_pizza(['onion','shrimp','goda cheese','mushroom'])
make_pizza(['ham','extra meat','sweet con','pepperoni'], 17)

You could also keep the toppings as arbitary positional arguments and make size a keyword-only parameter with default (Python3-only):

def make_pizza(*toppings, size=15):
    print("\nMaking a {}-inch pizza with the following toppings: ".format(size))
    for topping in toppings:
        print("- " + topping)

make_pizza('onion','shrimp','goda cheese','mushroom')
make_pizza('ham','extra meat','sweet con','pepperoni', size=17)
like image 23
MSeifert Avatar answered Jan 05 '23 23:01

MSeifert