4

When I run the python code below,

def main():
    #print prime_factors(10)
    print prime_factors(9)

def prime_factors(n, i=2, factors=[]):
    if n==1:
        return factors
    if(n%i==0):
        factors.append(i)
        n = n/i
        return prime_factors(n, i, factors)
    else:
        return prime_factors(n, i+1, factors)

if __name__ == '__main__':
    main()

it returns the expected results, it returns the prime factors of 9:

[3, 3]

If I remove the comment from line 2 "print prime_factors(10)", something strange happens. For 10 everything is fine, but for 9 it does not only contain the prime factors of 9, but those of 10, as well:

[2, 5]
[2, 5, 3, 3]

If I call the function with the two optional arguments

def main():
    print prime_factors(10, i=2, factors[])
    print prime_factors(9, i=2, factors[])

everything works fine.

[2,5]
[3,3]

I can't figure out why. I suspect this is some issue with scopes, but I just don't understand it :-( Any help would be appreciated.

  • `Least Astonishment in Python: The Mutable Default Argument` is definitely the #1 reason for duplicate closures of python questions... – Bakuriu Jul 09 '13 at 20:32
  • 1
    @AshwiniChaudhary: it's a duplicate, but how are new users supposed to know that from the title? – Jace Browning Jul 09 '13 at 20:34
  • 2
    @JaceBrowning But those who know this can mark this as duplicate. No need to answer the same question again and again. – Ashwini Chaudhary Jul 09 '13 at 20:37
  • 1
    Searching for `[python] default value change` returns that question as 4th result(at least for me). – Bakuriu Jul 09 '13 at 20:41

4 Answers4

7

The default values defined for function arguments are "sticky" - they belong to the function body itself, so when you modify them they stay modified for the next call.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
4

This is because factors is a mutable default argument. A new list called factors is evaluated only once, and hence if you mutate the list, you get the mutated list in the succeeding calls. See the following snippet -

>>> def test(a = []):
        a.append('x')
        return a

>>> test()
['x']
>>> test()
['x', 'x']

Try initializing it's default to None and then checking on None and assigning an empty list within the function body.

>>> def test(a = None):
        if a is None:
            a = []
        a.append('x')
        return a

>>> test()
['x']
>>> test()
['x']
Sukrit Kalra
  • 33,167
  • 7
  • 69
  • 71
0

Having factors=[] as a default value in function argument is not a good idea. Python evalutes the [] expression only once, when it first encounters the function definition. Basicly that means that all you calls to the prime_factors will use same list, and each call will append its own values.

Ex:

prime_factors(9)
prime_factors(9)
prime_factors(9)

will return

[3,3,3,3,3,3]
Blaž Šnuderl
  • 328
  • 4
  • 11
0

See this question: "Least Astonishment" and the Mutable Default Argument

You want to do something like:

...
def prime_factors(n, i=2, factors=None):
    if factors is None:
        factors = [] 
    ...
Community
  • 1
  • 1
Jace Browning
  • 11,699
  • 10
  • 66
  • 90