1

I noticed a strange behaviour in python, could anyone give me the reason for such behaviour?

If I call a function without attribute then the default value is retains its state. You can see the following example for better understanding

class EvenStream(object):
    def __init__(self):
        self.current = 0

    def get_next(self):
        to_return = self.current
        self.current += 2
        return to_return


class OddStream(object):
    def __init__(self):
        self.current = 1

    def get_next(self):
        to_return = self.current
        self.current += 2
        return to_return


def print_from_stream(n, stream=EvenStream()):
    for _ in range(n):
        print(stream.get_next())


print_from_stream(2)
print('*****')
print_from_stream(2)
print('*****')
print_from_stream(2, OddStream())
print('*****')
print_from_stream(2, OddStream())

OUTPUT: I was expecting 0,2,0,2 but it gave me 0,2,4,6

0
2
*****
4
6
*****
1
3
*****
1
3
Arghya Saha
  • 5,599
  • 4
  • 26
  • 48
  • 4
    Possible duplicate of ["Least Astonishment" and the Mutable Default Argument](https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument) –  Oct 01 '18 at 18:21

1 Answers1

6

What's happening?

When python sees

def print_from_stream(n, stream=EvenStream()):

It evaluates EvenStream() as the default parameter. This is one, definite, object. Each time you call print_from_stream with a default parameter, it uses the same instance that was created when EvenStream() was first evaluated. print_from_stream now has an associated object default_stream. This object is never updated with a new EvenStream. In C++ lingo, you've created a static variable.

The Fix

Make the default parameter of print_from_stream something like None. Then, add

if stream is None:
    stream = EvenStream()

to the top of your function. This will make a new instantiation of EvenStream() every time you call your function with a default parameter.

Note that its slightly different than before, as print_from_stream(10, None) will now work and provide even numbers, as opposed to crashing. This is still accepted in Python as good practice, similar to how passing undefined in a JS function is the same as if you didn't give any parameters. It lets users skip over the 2nd argument to get to the third as well, if they don't want to name their parameters.

Nicholas Pipitone
  • 4,002
  • 4
  • 24
  • 39
  • I have implemented the fix. I'm more interested in the reason – Arghya Saha Oct 01 '18 at 18:25
  • @argo I gave the reason up top. `EvenStream()`, your default parameter, only gets evaluated once. That is now the permanent default value. – Nicholas Pipitone Oct 01 '18 at 18:27
  • Your function `print_from_stream` instantiates a new `even_stream()` _object_ called `stream`. The _same_ object is then called again, which updates it's existing state and prints it. – G. Anderson Oct 01 '18 at 18:28
  • Nicholas provided the reason. The first time your function definition is evaluated by the interpreter, it evaluates all the arguments. When you *invoke* the function call, you don't reinstantiate a new object. – PMende Oct 01 '18 at 18:30