2

Here's a simplified version of the code in question:

class thing:
    def __init__(self, data1, data2={'foo': 1}):
        self.data2 = data2
        self.data2['bar'] = data1

datas = ['FIRST', 'SECOND']
things = [thing(x) for x in datas]
for p in things:
    print p.data2['bar']

I would expect this code to return:

FIRST
SECOND

However, it actually returns:

SECOND
SECOND

Why?


My best guess is:

  • that I am creating a single dictionary ur_dict = {'foo': 1},
  • that when I initialize an object of class thing I am not creating a new dictionary self.data2={'foo': 1} but rather initializing a reference to ur_dict,
  • and that when, in the constructor, I add the key-value pair bar: data1 to self.data2, I'm actually adding that key and value to ur_dict itself. This would update the data2 field of every single object in the list.

I tested this hypothesis by appending the following snippet to the code above:

things[0].data2['bar'] = 'FIRST'
for p in things:
    print p.data2['bar']

Sure enough, the result is:

FIRST
FIRST

So I think my hypothesis is probably correct, but it's still mysterious to me. My question seems very related to this question --- when I create a default value in a constructor, is this value a class variable instead of an instance variable? Why doesn't python create new objects for default values of arguments in an object? Is there a good way to conceptualize or to catch this kind of error in the future? What are some good references to learn about what's going on here?

(Apologies in advance if I mangled jargon; I'm a mathematician by formal education.)


Edit: @CrazyPython[::-1] mentioned this is a property of functions, not classes or objects. I created an example of a function with a mutable default argument and I'm trying to figure out how to break it in the manner I experienced with objects and classes above. Came up with this:

EXPONENT = 1
def fooBar(x, y=EXPONENT):
    return x**y
print EXPONENT
print foo(5)
EXPONENT = 2
print EXPONENT
print foo(5)

However, it returns:

1
5
2
5

What's a good way to "break" this to illustrate why not to use a mutable default argument here?

Community
  • 1
  • 1
Neal
  • 123
  • 1
  • 5

1 Answers1

2

self.data2={'foo': 1} is a mutable default argument. Never do that. 99% of the time it's a mistake.

def foo(a, b={}):
    ...

is not equivalent to

def foo(a, b=None):
    if b is None:
        b = {}

b is not reconstructed every time. It's the same object.

Is there a good way to conceptualize or to catch this kind of error in the future?

Use PyCharm or another good editor

when I create a default value in a constructor, is this value a class variable instead of an instance variable?

no no no no no no. It's tied to the function, not the class or instance. This is behavior applies to all functions. It has nothing to do with classes or your linked question.

Why doesn't python create new objects for default values of arguments in an object?

Performance and compatibility. Constructing an object is a potentially expensive operation.


Addendum

  • Please use CamelCase for classes. Not doing so is a PEP 8 violation, which is the Python official style guide.
  • Get a decent editor. Personally, I recommend PyCharm. (I'm no salesperson) It's free.
    • PyCharm can rename variables, highlight PEP 8 errors, perform advanced auto complete, type checking, spot mutable default arguments, and much more.
  • You aren't inheriting off of object. That's bad.*

    Why does this python code alter the field of every object in a list?

  • Without context, that's a bad title. It asks about the code. When we read the title, we don't have the code.


Making it more clear

When a function is stated, the default arguments are evaluated. (not when it is executed, when it is stated) They are tied to the function. When a function is executed, a copy of the default arguments is not produced. You directly modify the default arguments. The next function that is called will receive the same object, with modifications.

*You seem to be a professor or the like. Please don't be offended by a meme. That's just the risk of getting advice from the internet.

noɥʇʎԀʎzɐɹƆ
  • 9,967
  • 2
  • 50
  • 67
  • 1
    Ahh, "mutable default arguments" is the words for this phenomenon. I'm not sure what you mean by "it's tied to the function," I'll edit an example into my question. – Neal Aug 23 '16 at 18:17
  • @Neal I clarified. – noɥʇʎԀʎzɐɹƆ Aug 23 '16 at 18:26
  • 1
    @Neal It has nothing to do with classes or instances. This behavior occurs wherever there exists a function – noɥʇʎԀʎzɐɹƆ Aug 23 '16 at 18:27
  • @uoɥʇʎPʎzɐɹC so when the default argument is None, it is assigned in the _execution_ but if it is _something else_ is it assigned in the function definition? –  Aug 23 '16 at 18:28
  • @J.C.Rocamonde it doesn't matter what the value is. Every value is assigned when the function is stated. But you can't modify `None`. It's immutable, so there's no way of changing it to affect the function the next time it's run. Python's variables are references. – noɥʇʎԀʎzɐɹƆ Aug 23 '16 at 18:30
  • 1
    @uoɥʇʎPʎzɐɹC So for example it would be "safe" to assign a frozenset or a tuple as a default variable? (Safe, but not good practice.) – Neal Aug 23 '16 at 18:36
  • 1
    (Also, I'm not offended. Was a math teacher for a while, but am no longer -- I perfectly understand the impulse to use memes on students :) – Neal Aug 23 '16 at 18:37
  • 1
    @Neal It's safe and good practice. Assignment doesn't change the variable, it just changes the location the variable points too. – noɥʇʎԀʎzɐɹƆ Aug 23 '16 at 18:38
  • @Neal good. And you might want to familiarize yourself with the memes of meta just in case.... http://meta.stackexchange.com/q/19478/282673 Did you try PyCharm? – noɥʇʎԀʎzɐɹƆ Aug 23 '16 at 18:42
  • @Neal Using immutable default arguments is pretty much the whole point of default arguments. – noɥʇʎԀʎzɐɹƆ Aug 23 '16 at 18:43
  • Sidenote: If this is Python 3 code, inheriting explicitly from `object` is unnecessary. Only on Python 2 do you need to do that (to ensure you define new-style classes; Py 3 only has new-style). – ShadowRanger Aug 23 '16 at 18:53
  • @ShadowRanger Yes, but it's still good form to inherit off of object. Unnecessary, but good form. – noɥʇʎԀʎzɐɹƆ Aug 23 '16 at 18:54
  • @uoɥʇʎPʎzɐɹC: In Py3? Not at all. Inheritance from `object` is implicit and automatic. It serves no purpose whatsoever to explicitly list it in the inheritance hierarchy. – ShadowRanger Aug 23 '16 at 19:07
  • @Neal and unlike for mathematics, Stack Overflow is THE resource instead of A resource. https://i.imgur.com/wOsEq7N.png – noɥʇʎԀʎzɐɹƆ Aug 23 '16 at 19:12
  • @ShadowRanger Not according to PEP 8. And compatibility, dude. – noɥʇʎԀʎzɐɹƆ Aug 23 '16 at 19:12
  • @uoɥʇʎPʎzɐɹC: You're thinking of `import this`/Zen of Python; PEP8 says nothing about that. The guideline that "Explicit is better than implicit" is one of the most widely violated in Python, because implicit stuff for the simple cases makes the code less cluttered with redundant kruft. As for compatibility, writing code that runs without conversion in Py2 and Py3 is, at this point, hamstringing yourself. Besides, you can always just put `__metaclass__ = type` at the top of your file to make it use implicit new-style classes in Py2 just like in Py3. :-) – ShadowRanger Aug 23 '16 at 19:19
  • @ShadowRanger Common code says otherwise. – noɥʇʎԀʎzɐɹƆ Aug 23 '16 at 20:39
  • @ShadowRanger e.g. most libs – noɥʇʎԀʎzɐɹƆ Aug 23 '16 at 20:39