1

I have a function (which I'll call foo) that modifies a list (which I'll call my_list). foo does not always want to modify my_list in the same way; its behavior should influenced by its other arguments (which I'll call other_inputs). Here's some pseudocode:

def foo(my_list, other_inputs):
    for input in other_inputs:
        my_list.bar(input)
    return my_list

I can see two ways to format other_inputs.

I could use *args:

def foo(my_list, *other_inputs):
    for input in other_inputs:
        my_list.bar(input)
    return my_list

Alternately, I could make other_inputs a list, empty by default:

def foo(my_list, other_inputs=[]):
    for input in other_inputs:
        my_list.bar(input)
    return my_list

I've tested it on my machine and both options seem to do the same thing. Which one is preferable?

(Assume that this foo() is called many times, each time with a new other_inputs read in from some external source. Also assume that other_inputs is never appended to or mutated in any other way between external reads, so this isn't a problem.)

Community
  • 1
  • 1
Logan Davis
  • 117
  • 2
  • 12
  • 2
    It depends on how you want to call `foo`: `foo(some_list, a, b, c)` or `foo(some_list, [a, b, c])`. – chepner Aug 20 '15 at 16:30
  • 2
    [this answer][1] explains why mutable default args are a bad idea [1]: http://stackoverflow.com/q/1132941/473285 – scytale Aug 20 '15 at 16:30
  • @scytale: he linked that answer in his last sentence.. – DSM Aug 20 '15 at 16:31
  • 4
    I think the default empty list approach is a bad idea because of the well-known mutating problem which you refer to. Even if currently your code doesn't mutate it you might in the future modify the code in a way that does. Why place minefields in your backyard just because you don't currently use that part of the yard? – John Coleman Aug 20 '15 at 16:34

2 Answers2

1

Since you are reading other_inputs from another source, you presumably already have a sequence. That argues for the second approach:

def foo(my_list, other_inputs=None):
    if other_inputs is not None:
        # Assume other_inputs is some type of iterable
        for input in other_inputs:
            my_list.bar(input)
    return my_list

However, you could still use the first approach and call foo with

foo(some_list, *inputs)

It's mostly a matter of preference.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • In my particular case, I think this is the correct answer. Future readers, if you are not CERTAIN that you will never modify your code to edit the list other_inputs, then follow the advice in John Coleman's comment on my question and use *args. – Logan Davis Aug 20 '15 at 21:49
  • 1
    Note that John Coleman wasn't arguing against a list input, just against using a list as the argument default in the function definition. – chepner Aug 20 '15 at 22:31
0

Obviously both of the options are correct, it would be wrong to say one of them is not.

If all other arguments passing to be function are of same type(by same type means they all change input then), then both approaches are equivalent. It is just a matter of preference as suggested by @chepner

But if there is some additional argument (say , expected length of output) which is going to be used differently from other params, then using list explicitly would be a better design.

hspandher
  • 15,934
  • 2
  • 32
  • 45