bad recursion, still works

I

iu2

Hi,

I wrote this wrong recursive function that flattens a list:

def flatten(lst, acc=[]):
#print 'acc =', acc, 'lst =', lst
if type(lst) != list:
acc.append(lst)
else:
for item in lst:
flatten(item)
return acc

a = [1, 2, [3, 4, 5], [6, [7, 8, [9, 10], 11], 12], 13, 14]
b = flatten(a)
print b

I was amazed to realize that it flattens the list alright. Why? 'acc'
should be an empty list on each invocation of flatten, but is seems to
accumulate anyway...
 
M

mdsherry

Hi,

I wrote this wrong recursive function that flattens a list:

def flatten(lst, acc=[]):
    #print 'acc =', acc, 'lst =', lst
    if type(lst) != list:
        acc.append(lst)
    else:
        for item in lst:
            flatten(item)
    return acc

a = [1, 2, [3, 4, 5], [6, [7, 8, [9, 10], 11], 12], 13, 14]
b = flatten(a)
print b

I was amazed to realize that it flattens the list alright. Why? 'acc'
should be an empty list on each invocation of flatten, but is seems to
accumulate anyway...

When you say acc=[] in the function declaration, it binds acc to a
particular list object, rather than to a concept of an empty list.
Thus, all operations being performed on acc are being performed on the
same list. If, after the sample code you provided, were to call

c = flatten([15,16,17,[18,19]])
print c

you would get back the list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19].

Mark Sherry
 
I

iu2

I wrote this wrong recursive function that flattens a list:
def flatten(lst, acc=[]):
    #print 'acc =', acc, 'lst =', lst
    if type(lst) != list:
        acc.append(lst)
    else:
        for item in lst:
            flatten(item)
    return acc
a = [1, 2, [3, 4, 5], [6, [7, 8, [9, 10], 11], 12], 13, 14]
b = flatten(a)
print b
I was amazed to realize that it flattens the list alright. Why? 'acc'
should be an empty list on each invocation of flatten, but is seems to
accumulate anyway...

When you say acc=[] in the function declaration, it binds acc to a
particular list object, rather than to a concept of an empty list.
Thus, all operations being performed on acc are being performed on the
same list. If, after the sample code you provided, were to call

c = flatten([15,16,17,[18,19]])
print c

you would get back the list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19].

Mark Sherry

I still don't understand: In each recursive call to flatten, acc
should be bound to a new [], shouldn't it? Why does the binding happen
only on the first call to flatten?
 
M

mdsherry

Hi,
I wrote this wrong recursive function that flattens a list:
def flatten(lst, acc=[]):
    #print 'acc =', acc, 'lst =', lst
    if type(lst) != list:
        acc.append(lst)
    else:
        for item in lst:
            flatten(item)
    return acc
a = [1, 2, [3, 4, 5], [6, [7, 8, [9, 10], 11], 12], 13, 14]
b = flatten(a)
print b
I was amazed to realize that it flattens the list alright. Why? 'acc'
should be an empty list on each invocation of flatten, but is seems to
accumulate anyway...
When you say acc=[] in the function declaration, it binds acc to a
particular list object, rather than to a concept of an empty list.
Thus, all operations being performed on acc are being performed on the
same list. If, after the sample code you provided, were to call
c = flatten([15,16,17,[18,19]])
print c
you would get back the list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19].
Mark Sherry

I still don't understand: In each recursive call to flatten, acc
should be bound to a new [], shouldn't it? Why does the binding happen
only on the first call to flatten?

Default values are bound when the function is defined, not when it's
called. For example,
... print bar
...0.632312549821312

If you view [...] just as shorthand for list(...), it might make a bit
more sense. For immutable values, one can't change the value bound to
the name, only what the name is bound to, so this behaviour is less
obvious. But still there.

As to why default values are evaluated at define time vs. call time,
I'd argue reasons of scope and speed - if the default value is a
computed constant, it makes little sense to recompute it every time
the function is called.

Mark Sherry
 
M

Michael Torrie

iu2 said:
I still don't understand: In each recursive call to flatten, acc
should be bound to a new [], shouldn't it? Why does the binding happen
only on the first call to flatten?

Nope. In each new call it's (re)bound to the same original list, which
you've added to as your function continues--it's mutable. Default
variables that are bound to mutable objects are one of the big caveats
that is mentioned in the FAQ.
 
I

iu2

iu2 said:
I still don't understand: In each recursive call to flatten, acc
should be bound to a new [], shouldn't it? Why does the binding happen
only on the first call to flatten?

Nope.  In each new call it's (re)bound to the same original list, which
you've added to as your function continues--it's mutable.  Default
variables that are bound to mutable objects are one of the big caveats
that is mentioned in the FAQ.

Thanks guys, it's clear now
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,586
Members
45,084
Latest member
HansGeorgi

Latest Threads

Top