0

i wrote 2 simple codes in python that should make the same work, but it doesnt. if you can tell me why is this, and also explain me more about the "everything is an object in python". thanks :)

def f(p=[]):
    print p
    f.a += 1
    print id(p)
    if(f.a == 1):
        p = p + [4]
    else:
        p = p + [5]
    return p
f.a = 0
f()
f()
f()

answer:

[]
40564496
[]
40564496
[]
40564496
def f(p=[]):
    print p
    f.a += 1
    print id(p)
    if(f.a == 1):
        p += [4]
    else:
        p +=[5]
    return p
#print f()
#print f()
f.a = 0
f()
f()
f()

answer:

[]
40892176
[4]
40892176
[4, 5]
40892176

like you see - the first code every time does a new list, and the second add to the last used one...

Kevin M Granger
  • 2,375
  • 23
  • 28
mikel bubi
  • 123
  • 1
  • 1
  • 7

2 Answers2

1

You should never* pass a mutable object as a default value in python function as there will be just one. Consequently your second function always uses the same list, that was instationed during function creation. First one, on the other hand creates new lists during p = p + [4] assignments, thus there is no mutation of the default arg. Code this this way:

def f(p=None):
    if p is None:
        p = []

    print p
    f.a += 1
    print id(p)
    if(f.a == 1):
        p += [4]
    else:
        p +=[5]
    return p

* by never I mean "unless you really know what you are doing". There is always an exception from the rule, but if you need to pass a mutable as a default value, you need to understand its consequences very well, thus you would probably not be reading this answer anyway. In particular, it is discouraged in many Python style guides, such as Google python style guide

Do not use mutable objects as default values in the function or method definition.

This should also cause pylint warning W0102

dangerous-default-value (W0102): Dangerous default value %s as argument Used when a mutable value as list or dictionary is detected in a default value for an argument.

lejlot
  • 64,777
  • 8
  • 131
  • 164
  • It's not that you should **never** pass mutable objects as default values, rather, you should understand what it means. – juanpa.arrivillaga Jun 28 '16 at 21:34
  • @juanpa - sure, there is always an exception (see updated answer). – lejlot Jun 28 '16 at 21:36
  • I would not say, "*never* pass [a] mutable object as a default value." There are certain instances when such behavior is desired and useful (i.e. writing a memoization decorator). – pzp Jun 28 '16 at 21:36
1

The operators obj += other and obj = obj + other are not the same. The previous often uses inplace modification, whereas the later usually creates a new object. In the case of lists, obj += other is the same as obj.extend(other).

The second important thing is that def statements are only evaluated once per scope. If you define a function at module scope, def is evaluated once - that includes the creation of its default parameters!

def foo(bar=[]):
  print(id(bar))

In this case, bar will always default to the same list object that has been created when def foo was first evaluated. Whenever you modify the default value, it will carry over to the next time this default is used. Contrast this with:

def foo(bar=None):
  bar = bar if bar is not None else []
  print(id(bar))

In this case, the default list is recreated every time the function is called.

MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119