What the default arguments are?

Some sources point out that arguments might be default. But they are not clear in what these arguments are. Sometimes it looks like the default arguments are mismatched with keyword-only parameters or positional and keyword parameters. E.g.:

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Alice")  
greet("Bob", "Hi")  

One source even suggests that the value of greeting (i.e. “Hello”) is that default argument. What I can see here are positional and keyword parameters and then positional arguments. Does it mean, that default arguments are those, which are passed to the function, but not defined in the function call?

What about this code:

def veta(jmeno = "Lady", plemeno = "britské krátkosrsté"): 
    print(f"Kočka {jmeno} je plemeno {plemeno} kočky.")
    
veta()

is that empty function call a default argument, or default arguments are those two values of keyword parameters in the function definition?

Yes, they’re all default arguments, other than name which is a required positional arg. Not specifiying them in the function call means Python will use the default values specified in the function’s header (in its definition).

1 Like

More proper terminology would be not default argument, but default argument value or simply default value (of the argument), as in the docs.

Basically, whatever goes after = in the function definition is a default value of the corresponding argument. It’s computed only once — when the function is defined, and is internally stored inside the function object. Then every time you call a function, all argument names mentioned in the definition must get some value — so either you provide it yourself, or otherwise the default value will be used, and it does not matter here, if these arguments are positional or keyword.

Here, name and greeting have double nature — they can be both positional or keyword arguments depending on how you later call the function. You can think of it as follows:

  • name stands for both 0 (position) and name (keyword);
  • greeting stands for both 1 (position) and greeting (keyword), and has a default value.

Then in every function call you need to supply 0 or name yourself, because it does not have a default value, but for 1 or greeting it’s up to you — the default value will be used if you do not provide the value yourself. In your examples:

  • greet("Alice") sends "Alice" to argument 0 which is also name, but the default value "Hello" is used for argument 1/greeting, because you did not provide it;
  • greet("Bob", "Hi") sends "Bob" to argument 0/name and "Hi" to 1/greeting.

Another example:

  • greet(greeting="Hola") is not a valid call, because you provide 1/greeting but there is no value for 0/name.

Here both 0/jmeno and 1/plemeno have default values. When you call veta(), both default values are used, since you do not supply them yourself.

1 Like

Thanks for that information and for the link to the official Python documentation.

This, quoted from that documentation, is significant regarding default arguments that are mutable objects:

Important warning: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. …

Essentially, if you modify a mutable object that is passed as a default, it can affect subsequent calls to the function that rely on the default.

The above does not affect the examples given in the original post, but may be important for future reference when working with lists or dictionaries as default arguments.

1 Like

I’m probably wrong, but is it possible that Python allows you to either allocate default values for all arguments of a functions or none of them? Or am I mistaken with something else?

You can have defaults on none, some, or all arguments. They all behave the same way: first the actual arguments (from the call) are allocated to the parameters, and then the ones that don’t have any value get their defaults. (And then if there’s anything that still doesn’t have a value assigned, that’s an error.)

Is that what you meant?

Yes, this is important to bear in mind. It often won’t matter; it’s only really a concern when (a) the value is mutated within the function, and (b) you don’t WANT that change. For example, this code won’t be a problem:

def frobnicate(stuff=[]):
    for thing in stuff:
        thing.frob()

since there’s no situation in which the mutability of the list would be a problem. And neither is this a problem:

def fibonacci(n, *, cache={}):
    if n in cache: return cache[n]
    if n < 2: return n
    cache[n] = fibonacci(n - 1) + fibonacci(n - 2)
    return cache[n]

as the retention of the cache is quite deliberate here.

1 Like

Indeed, it’s a feature not a bug, and can be used to great advantage, as you have shown, provided that one is aware of this feature.

1 Like

Yes, thank you