-1

I have the following setup:

def returnList(arg=["abc"]):
    return arg

list1 = returnList()
list2 = returnList()

list2.append("def")

print("list1: " + " ".join(list1) + "\n" + "list2: " + " ".join(list2) + "\n")

print(id(list1))
print(id(list2))

Output:

list1: abc def
list2: abc def

140218365917160
140218365917160

I can see that arg=["abc"] returns copy of the same default list rather than create a new one every time.

I have tried doing

def returnList(arg=["abc"][:]):

and

def returnList(arg=list(["abc"])):

Is it possible to get a new list, or must I copy the list inside the method everytime I want to some kind of default value?

varesa
  • 2,399
  • 7
  • 26
  • 45
  • Default arguments are evaluated during compilation of the function, you can't work around this fact. This question gets asked every second day and you can easily find multiple ways to deal with it via google... the most common one is setting the default argument to `None` and adding a check like `if arg==None: arg=[]` at the beginning of the function. – l4mpi Aug 01 '13 at 10:38
  • I was pretty sure this has been asked before, but I could not figure out the correct search terms. I went through many pages of unrelated stuff until giving up. It is annoying when you know it exists, but don't know how to find it... – varesa Aug 01 '13 at 10:44
  • Searching `python default argument list` yields pretty good results in google... – l4mpi Aug 01 '13 at 10:58
  • @l4mpi I don't remember what I searched for late last night, but I remember I tried to describe the problem. However the fact that I was almost going to fall asleep might have been a factor. My bad, should have re-searched this morning – varesa Aug 01 '13 at 11:04

2 Answers2

5

The nicest pattern I've seen is just

def returnList(arg=None):
    if arg is None: arg = ["abc"]
    ...

The only potential problem is if you expect None to be a valid input here which in which case you'll have to use a different sentinel value.

The problem with your approach is that args default argument is evaluated once. It doesn't matter what copying operators you do because it's simply evaluated than stored with the function. It's not re-evaluated during each function call.

Update:

I didn't want nneonneo's comment to be missed, using a fresh object as a sentinel would work nicely.

default = object()
def f(x = default):
    if x is default:
        ...
daniel gratzer
  • 52,833
  • 11
  • 94
  • 134
  • 1
    Just to add to the good answer here: a good value for a "different sentinel value" is a freshly-constructed object (e.g. `object()`) stored specifically for the purpose of acting as a sentinel. For example, `default = object(); def doSomething(arg=default): if arg is default: ...` – nneonneo Aug 01 '13 at 11:00
  • I added your comment since I didn't want future readers to miss it :) – daniel gratzer Aug 01 '13 at 11:04
  • `def default = object()` is not valid Python. Drop the `def`. – Martijn Pieters Aug 01 '13 at 11:46
  • @MartijnPieters Thanks, that's what I get before coding before morning coffee – daniel gratzer Aug 01 '13 at 11:51
  • Okay, I'm just nitpicking, but this way you clutter your namespace (with the name `default` in this case). To avoid that, you could `del default` after your function definition. Or use a different global with a fitting name (e. g. `Ellipsis` which has about the meaning of "being left out") which has an even lower chance of appearing as a value than `None`. – Alfe Aug 01 '13 at 12:02
1

What you want is what you state yourself: You want a new copy of the value each time. Simply calling a function does not do anything automatically, so there is no way to have this accomplished just by a syntactic trick. But you can use a decorator to do what you want:

import copy

def keepDefaults(f):
  defaults = f.func_defaults
  def wrapped(*args, **kwargs):
    f.func_defaults = copy.deepcopy(defaults)
    return f(*args, **kwargs)
  return wrapped

@keepDefaults
def f(a=[1]):
  return a

f()[0].append(2)
print f()  # will print "[1]" instead of "[1, 2]"
Alfe
  • 56,346
  • 20
  • 107
  • 159
  • 2
    Really, this is a solution? You do know this is totally not thread-safe, right? Nor does this kind of hackery win you any friends in a larger team. – Martijn Pieters Aug 01 '13 at 10:59
  • To make it thread-safe (if this is a requirement), use the normal mechanisms like locks etc. Out of topic here. Please elaborate on why you think this is a *hack*, more than other decorators which change the behavior of their wrapped function somehow. – Alfe Aug 01 '13 at 11:04
  • It is a hack because you are violating normal expectations of any moderately experienced python dev. – Martijn Pieters Aug 01 '13 at 11:07
  • All decorators do that, more or less. I would accept that using internals like the mentioned `func_defaults` is rather low-level, but as a whole the original behavior of Python is breaking the rule of least astonishment in the beginning (I guess we all agree on that, after all it's on the list of these). So, modifying a function so that the originally expected behavior appears *using a well-named and hopefully well-documented way* like the decorator does not look so hacky to me. – Alfe Aug 01 '13 at 11:10
  • I find your critique understandable and I also appreciate it, as it shows me what others think of my ideas. But I can't agree on all aspects ;-) – Alfe Aug 01 '13 at 11:13
  • So, instead of teaching people that function signatures are created once you create a decorator instead that panders to the initial wrong assumptions instead of teaching them how this works? Nope, still a hack to me. Yup, decorators are great tools, just not in this case. – Martijn Pieters Aug 01 '13 at 11:17
  • Don't take this out of context. I would not have given my solution as first and only one. I just saw no reason to repeat jozefg's "teachings" (as you put it). I wanted to utter further thoughts to make clear that there is a different approach possible in Python. I never wanted the OP to not understand the mechanism. It just seemed to be explained enough already. – Alfe Aug 01 '13 at 11:20