One of the useful magic method is __del__, which is the python destructor. It doesn't implement the behavior of del(x) instead, it defines the behavior for when an object is garbage collected. It is useful for objects that require extra cleanup like close files. The only required property of Python garbage collection is that it happens after all references have been deleted, so this might not necessary happen right after and might not happen at all.

An instance can live longer than expected like in case of propagating exceptions or cyclic references. Since there is no guarantee that this code is executed, we can not use it to handle cases of exception but use finally for try block

An example usage of __del__ could be

from os.path import join

class FileObject:
    '''Wrapper for file objects to make sure the file gets closed on deletion.'''

    def __init__(self, filepath='~', filename='sample.txt'):
        # open a file filename in filepath in read and write mode
        self.file = open(join(filepath, filename), 'r+')

    def __del__(self):
        del self.file

In above case, if the file operation results in exception, it may call __del__ and close the file. But it is recommended to put the file operation in a try block and the file closure should be done in finally block

If you know that the destructor provides a required cleanup, you might want to call it directly: x.__del__(). Obviously, you should do so, only if you know that it doesn't mind to be called twice.