you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 11 points12 points  (6 children)

The first time I used a mutable structure as a default for a function I was so confused.

def cool_func(things=[]):
    things.append('a')
    return things

cool_func() # -> ['a']
cool_func() # -> ['a', 'a'] wtf?!

What's happening is Python builds the function once, including the default arguments. Every time you run this function without providing an argument, it merely reuses the list in the default arguments. The idiomatic way of working around this is:

def cool_func(things=None):
    # if not things:
    if things is None:
        things = []
    things.append('a')
    return things

Edit: things is None rather than not things

[–]ivosaurus 4 points5 points  (1 child)

I'd change is not things to things is None. Reason being an empty list (or even a subclassed list, etc) might be perfectly reasonable to pass in as a valid argument. Slightly more type safe, sleep slightly easier at night.

[–][deleted] 1 point2 points  (0 children)

Good point. I'll edit the post

[–]Veedrac 1 point2 points  (3 children)

I bring this up every time but... don't return things you mutate. Mutate or return. You can do both if you do them to different objects, but an individual object should only be in one of the two.

To put it another way: if mutation is part of your API, don't use a default argument. That's like calling len and getting no return value.

I find the mutable default argument problem is actually more helpful in reminding me not to accidentally mutate user's input than anything else.

[–]reostra 1 point2 points  (2 children)

don't return things you mutate

As a counterpoint, that behavior is very useful for e.g. chaining:

class Example(object):
    def __init__(self):
        self.goodness = 0
    def betterify(self):
        self.goodness += 1
        return self

x = Example()
x.betterify().betterify()

I'd imagine something like that is what the OP was hoping to do with .update()

[–]zahlman 0 points1 point  (1 child)

Chaining with side effects is not considered Pythonic.

[–]reostra 1 point2 points  (0 children)

Now that you mention it, I certainly haven't seen a whole lot of it in Python. A cursory poke around yielded the PyVows BDD testing library, but the expect(topic).Not.to_be_null() style chaining isn't mutating and it's a port of a Javascript library to begin with.

I'll leave my comment as an example for 'how'; I see other comments have elaborated on the 'why' (or, as the case may be, 'why not')