0

This is my very first question to StackOverflow. If the format of this question is not standard, please feel free to let me know.

I have been transiting from Matlab to Python after I found out about iPython. I use VSCode and Jupyter interface along with "# %%" header in order to get the interactive console. So far, I love everything and I feel like full transition is needed for my work.

I have been having some mysterious issue with initialising class instance.

Suppose we have two minimal files that recreate the issue:

main.py

# %%
%reset -f
from dummy import Dummy
dummy = Dummy()
print('created = %s' % dummy.others)
dummy.receive('something')
print('received = %s' % dummy.others)

dummy.py

class Dummy():
    def __init__(self, others=[]):
        self._others = others
        pass

    def receive(self, value):
        self._others.append(value)

    @property
    def others(self):
        return self._others

When I run main.py for the first time after restarting the Jupyter kernel, I get

created = []
received = ['something']

which is totally expected. But, if I run it again, I get

created = ['something']
received = ['something', 'something']

indicating that "dummy" variable is not initialised as it should.

In order to track down the source, I changed the scripts so that

main.py (debugging)

# %%
%reset -f

from dummy import Dummy
try:
    print(dummy.others)
except:
    print('dummy not found')

dummy = Dummy()
print('created = %s' % dummy.others)
dummy.receive('something')
print('received = %s' % dummy.others)

dummy.py (debugging)

class Dummy():
    def __init__(self, others=[]):
        print('input = %s' % others)
        self._others = others
        print('internal = %s' % self._others)
        pass

    def receive(self, value):
        self._others.append(value)

    @property
    def others(self):
        return self._others

When I run the main script after restarting the kernel, I get

dummy not found
input = []
internal = []
created = []
received = ['something']

and the below after another run

dummy not found
input = ['something']
internal = ['something']
created = ['something']
received = ['something', 'something']

It is so clear that "dummy" variable is successfully deleted after "%reset -f", but somehow input argument "others" in Dummy constructor is assigned to the previously assigned, rather than "[]".

But why?

When I change "def receive(self, value)" in dummy.py as follows, I don't have the problem anymore:

def receive(self, value):
    self._others = self._others + [value]

But again, this change is to with mutability if I understand correctly. It should affect the input argument assignment in the constructor.

Also, this issue doesn't appear when Dummy class is in the same file (main.py)

Could you please let me know what I am missing?

Edit: I now understand that I should be careful with mutable init parameters. But my other fundamental question is why "%reset -f" in iPython did not clear everything. It did delete dummy variable, but the init parameter "others" was mysteriously assigned to the previous. Isn't the magic thingy supposed to delete everything?

ZechFace
  • 5
  • 1
  • 3
  • Do not set the default value of your argument as a mutable, instead use `None` or a sentinel object. – Frodon Nov 30 '20 at 14:21
  • Also, `__init__` is the *initializer*; `__new__` is the constructor. – chepner Nov 30 '20 at 14:22
  • @Frodon: more question below. Thanks! – ZechFace Nov 30 '20 at 14:32
  • @chepner: I see... I thought they are the same. I would need to google to find out the difference between them. Thanks! – ZechFace Nov 30 '20 at 14:32
  • My other question is, why "%reset -f" did not delete everything fully. It did manage to delete "dummy" variable. But why do I have input parameter assigned? – ZechFace Nov 30 '20 at 14:32
  • Essentially, `__new__` is a static method that returns a new object (ultimately created by `object.__new__`), while `__init__` *receives* that object as an argument (via the `self` parameter) and typically sets attributes on that object, and notably does not *return* anything; it mutates the object in place. – chepner Nov 30 '20 at 15:49

1 Answers1

0

Here is a correct dummy.py script without a mutable as default value for the argument:

class Dummy():
    def __init__(self, others=None):
        self._others = others or []
        pass

    def receive(self, value):
        self._others.append(value)

    @property
    def others(self):
        return self._others

Output:

created = []
received = ['something']
Frodon
  • 3,684
  • 1
  • 16
  • 33
  • Thanks. New thing I learnt about Python today. But, another fundamental question is why "%reset -f"" did not delete "dummy" variable fully? The variable seemed deleted, but the input argument was weirdly assigned. – ZechFace Nov 30 '20 at 14:28
  • Sorry I don't know about the `% resetl -f` Jupyter command. – Frodon Nov 30 '20 at 14:43