Why cant we open a file inside a destructor method of a class?

class Test:

def __init__(self, number):

    self.number = number

    fh = open(r"Desktop\Now.txt", 'a')

    fh.write(f"New object having number {self.number} has been created\n")

    fh.close()

def __del__(self):

    fh = open(r"Desktop\Now.txt", 'a')

    fh.write(f"Object number {self.number} destroyed")

    fh.close()    

t1 = Test(1)

t2 = Test(2)

t3 = Test(3)

#Why does my code throw an error?

Iā€™m not an expert, but I think the open builtin is being deleted before the instances. Have a look at the warning in the documentation for __del__ here:

3. Data model ā€” Python 3.9.5 documentation.del

This works if you unbind the variables t1, t2 and t3 before interpreter shutdown:

class Test:
    def __init__(self, number):
    
        self.number = number    
        fh = open("/home/jean/tmp/now.txt", 'a')
        fh.write(f"New object having number {self.number} has been created\n")
        fh.close()
    def __del__(self):
        fh = open(r"/home/jean/tmp/now.txt", 'a')
        fh.write(f"Object number {self.number} destroyed")
        fh.close()    

t1 = Test(1)
t2 = Test(2)
t3 = Test(3)

del t1
del t2
del t3
1 Like

Hey thanks for replyingā€¦In the above caseā€¦you explicitly called the destructor method by deleting the reference variablesā€¦I am still not clear why this works but still it is helpfullā€¦I will let you know if I make any further progress on this!!

The __del__ method is only called after the object has no more
references. In CPython (the common implementation) objects are reference
counted, and __del__ is called as soon as the last reference
disappears. In other implementations that might happen later.

Jeanā€™s suggestion does not call the __del__ method. Running:

del t1

does not call t1.del. Instead, it removes the reference to the
object you got from Test(1) stored in ā€œt1ā€. Because that is the only
reference, in CPython the __del__ method gets called at that point.

Jeanā€™s point is that if you do not remove the reference yourself, it
gets removed at programme shutdown. The order in which those references
are removed is not specified - all sorts of things may be gone by that
point.

However, I do not expect that the open() function will be gone.

Instead, I suspect that self.number may be gone. At programme
shutdown, the order in which objects are destroyed is not specified.

If you had posted the error message and traceback you received we would
have a better idea. It is always important to include this information.

Cheers,
Cameron Simpson cs@cskk.id.au

2 Likes

Hi,

When asking ā€œWhy does my code throw an error?ā€ it helps if you show the
error and explain the circumstances under which it occurs.

I ran your code, and it works for me. There is no error when creating
three instances in the interactive interpreter:

>>> t1 = Test(1)
>>> t2 = Test(2)
>>> t3 = Test(3)
>>> 

and there is no error when deleting those instances either:

>>> del t1
>>> del t2
>>> del t3
>>> 

and the file is written to:

>>> with open(r"Desktop\Now.txt") as f:
...     print(f.read())
... 
New object having number 1 has been created
New object having number 2 has been created
New object having number 3 has been created
Object number 1 destroyedObject number 2 destroyedObject number 3 destroyed

So to answer your questions:

  • you can use open inside a __del__ method;

  • I have no idea why your code raises an exception, it works for me.

Are you running under a IDE or other non-standard environment? I guess
you are using Windows (from the use of a backslash in the file path) but
can you explain where and how you are getting an exception, and what the
exception is, please?

On further investigation, I find that I can cause an error by running
the code as a stand-alone script. Steps to reproduce the error:

  • save the code in a file, ā€œdeltest.pyā€

  • run python3 deltest.py

The error seems to be occurring during interpreter shutdown:

Exception ignored in: <function Test.__del__ at 0x7f955ad07f80>
Traceback (most recent call last):
  File "deltest.py", line 11, in __del__
NameError: name 'open' is not defined
Exception ignored in: <function Test.__del__ at 0x7f955ad07f80>
Traceback (most recent call last):
  File "deltest.py", line 11, in __del__
NameError: name 'open' is not defined
Exception ignored in: <function Test.__del__ at 0x7f955ad07f80>
Traceback (most recent call last):
  File "deltest.py", line 11, in __del__
NameError: name 'open' is not defined

This seems very oddā€¦ it looks like the builtins are being garbage
collected before other objects. I didnā€™t think that was possible. I
thought that the builtins were garbage collected last, not first.

I have tested this under both 3.9 and 3.7, and after making a few minor
changes to get the code to compile, under 3.5, and it fails every time.
But it works without error under Python 2.7.

Can somebody convince me that this is not a bug in the shutdown
procedure? Given that Python supports object finalisers, __del__, I
would expect that something as simple as this one should work even at
interpreter shutdown. Thereā€™s no complicated issues with cycles, or
threads, I would have expected something simple and straightforward:

  • delete the module globals t1, t2, t3
  • which runs their finalisers
  • only after that, delete builtins

but it seems that builtins is being deleted first. Why?

2 Likes

On further investigation, I find that I can cause an error by running
the code as a stand-alone script. Steps to reproduce the error:

  • save the code in a file, ā€œdeltest.pyā€
  • run python3 deltest.py

The error seems to be occurring during interpreter shutdown:

Exception ignored in: <function Test.__del__ at 0x7f955ad07f80>
Traceback (most recent call last):
 File "deltest.py", line 11, in __del__
NameError: name 'open' is not defined

[ā€¦]


This seems very odd... it looks like the builtins are being garbage
collected before other objects. I didn't think that was possible. I
thought that the builtins were garbage collected last, not first.

I thought it impossible too!

I have tested this under both 3.9 and 3.7, and after making a few minor
changes to get the code to compile, under 3.5, and it fails every time.
But it works without error under Python 2.7.

Can somebody convince me that this is not a bug in the shutdown
procedure? Given that Python supports object finalisers, __del__, I
would expect that something as simple as this one should work even at
interpreter shutdown. Thereā€™s no complicated issues with cycles, or
threads, I would have expected something simple and straightforward:

  • delete the module globals t1, t2, t3
  • which runs their finalisers
  • only after that, delete builtins

but it seems that builtins is being deleted first. Why?

Because ā€˜bā€™ comes before ā€˜_ā€™? Insertion ordered modules dicts? An
accident? A ruthless ā€œdonā€™t let users dependend on the cleanup orderā€?

But yeah, Iā€™d have thought a little nuance in interpreter shutdown might
leading to del code working more often than not. Whther thatā€™s a
good thing is debatable. (Not a fan of bugs which show up late eg after
deployment).

Cheers,
Cameron Simpson cs@cskk.id.au

Acceptance Testing: Dropping your mods straight into the production
environment to see if the users will accept them.

Yesā€¦I meant the same when I said that he was explicitly calling the del methodā€¦moreover,I knew that at the end of script all objects are made availabe for garbage collectionā€¦hence I tried this example at homeā€¦but it didnt workā€¦next time I post a question,I will post the error message I get along with my questionā€¦Thanks for helping.