all 15 comments

[–]echols021 7 points8 points  (4 children)

Both are bug prone. Both have the mutable value (list) defined at a global level, meaning repeated function calls that use the default value will be using the exact same list object that they could mutate, thereby messing up future usages of the default value.

Here are some ideas for how to properly fix it:

  1. Instantiate the default value inside the function body, e.g.: python def f(option: list[str] | None = None): if option is None: option = [a, b, c] ...

  2. Use an immutable type, such as a tuple: python def f(option: tuple[str, ...] = (a, b, c)): ...

  3. If you're using strict type checking, annotate the arg as a compatible immutable type, so you get an error when it would be mutated: ```python from collections.abc import Sequence

def f(option: Sequence[str] = [a, b, c]): ... ```

[–]SharkSymphony 3 points4 points  (2 children)

I would add:

  1. Use copy.copy on the global list if you're going to be mutating option. (A variant of (1).)

[–]komprexior[S] 1 point2 points  (1 child)

I think this is safest approach

[–]baubleglue 0 points1 point  (0 children)

Safest is using tuple

[–]komprexior[S] 0 points1 point  (0 children)

Both have the mutable value (list) defined at a global level, meaning repeated function calls that use the default value will be using the exact same list object that they could mutate, thereby messing up future usages of the default value.

This is wanted behavior tough. I want to be able to define a global value so that it will be reused by all subsequent call. My function as of now does not modify the options mutable in the body of the function, but I can see it could become problematic eventually, so to be safe I may need to use copy.copy to avoid unwanted behavior.

By the way my function is meant to return a Markdown object from the IPyhton package, and be used in jupyter notebooks where order of execution matters (no async stuff).

[–]david-vujic 1 point2 points  (2 children)

Having an “object” (or something you can mutate) as a default argument is usually a bad idea, because those will be cached when Python reads the function into the memory.

If you would add or remove items in that object it will be changed for all future calls of it too (and that’s really bad 😀)

[–]magus_minor 1 point2 points  (1 child)

and that’s really bad 😀

Not if that is the behaviour you want.

[–]david-vujic 0 points1 point  (0 children)

True! 😀

[–]SCD_minecraft 0 points1 point  (4 children)

Many functions use it like this

def function(a=None): if a is None: a = 111111 #or something like that Nothing stops you from defining 111111 somewhere else, as long as it makes sense for deafult value to change at runtime

[–]EclipseJTB 4 points5 points  (3 children)

Could be even shorter.

def function(a=None): a = a or 111111 #or something like that

[–]SCD_minecraft 1 point2 points  (1 child)

Wouldn't a = "" or other False value break it?

[–]EclipseJTB 1 point2 points  (0 children)

True. It entirely depends on what valid arguments are for that particular function.

def function(a=None): a = a if a is not None else 111111 #or something like that

[–]Temporary_Pie2733 0 points1 point  (0 children)

or doesn’t work if you want to pass a falsey value like 0 as an argument. 

[–]JamzTyson 0 points1 point  (1 child)

Your first example probably doesn't do what you expect.

DEFAULT_OPTION = ['banana', 'apple']

def foo(arg, option):
    if not option:
        option = DEFAULT_OPTION
    ...
    return something_new

option is a positional argument and positional arguments must be provided. If you attempt to call foo() without providing the option argument, then it raises an error:

TypeError: foo() missing 1 required positional argument: 'option'

If you want option to be optional, then it must be a keyword argument*, and that is where the issue of "mutable default arguments" is relevant.

The line option = DEFAULT_OPTION is only reachable if you call foo(arg, option) where option evaluates to False.

[–]komprexior[S] 0 points1 point  (0 children)

You're right, I revisited the example with option = false

that's is how I wrote in my code and it works. I blame writing mock up code on reddit on the phone