Destructors in Python

destructor | Python Methods and Functions

Destructors are called when the object is destroyed. In Python, destructors are not needed as much as in C++, because Python has a garbage collector that automatically handles memory management. 
The __ destructor method in Python. It is called when all references to the object have been removed, i.e. when an object is garbage collected. 
The syntax for declaring a destructor is:

 def __del __ (self): # body of destructor 

Example 1: Here a simple example of a destructor. Using the del keyword, we removed all references to obj, so the destructor was called automatically.

# Python program to illustrate the destructor

class Employee:

 

# Initialization

def __ init __ ( self ):

  print ( 'Employee created.' )

  

# Deleting (destructor call)

def __ del__ ( self ):

print ( 'Destructor called, Employee deleted.' )

 

obj = Employee ()

del obj

Output:

 Employee created. Destructor called, Employee deleted. 

Note: the destructor was called after the program terminated or when all references to the object have been removed, i.e. when the reference count goes to zero, not when the object is out of scope.

Example 2: This example explains the above note. Note here that the destructor is called after "End of program ..." is printed.

# Python program to illustrate the destructor

 

class Employee:

 

# Initialization

def __ init __ ( self ):

print ( 'Employee created' )

 

< code class = "comments"> # Call destructor

def __ del __ ( self ):

  print ( " Destructor called " )

 

def Create_obj ():

print ( 'Making Object ...' )

obj = Employee ()

print ( 'f unction end ... ' )

  return obj

 

print ( 'Calling Create_obj () function ...' )

obj = Create_obj ( )

print ( 'Program End. ..' )

Output:

 Calling Create_obj () function ... Making Object ... Employee created function end ... Program End ... Destructor called 

Example 3: Now consider the following example:

# Python destructor illustration program

  

class A:

def __ init __ ( self , bb):

self . b = bb

 

class B:

def __ init __ ( self ):

self . a = A ( self )

def __ del __ ( self ):

print ( " die " )

  

def fun ():

b = B ()

 
fun ()

Exit :

 die 

In this example, when fun () is called, it creates an instance of class B, which transfers itself to the class A which then sets a reference to class B and results in circular reference .

Normally the garbage collector in Python, which is used to detect this type of circular reference, will remove it, but in this example, using a custom destructor marks the item as "not collectable" ... 
He just doesn't know in what order to destroy objects, so he leaves them. Therefore, if your instances participate in circular references, they will live in memory as long as the application is running.





Destructors in Python: StackOverflow Questions

Answer #1

No need to hack around getting data from the git command yourself. GitPython is a very nice way to do this and a lot of other git stuff. It even has "best effort" support for Windows.

After pip install gitpython you can do

import git
repo = git.Repo(search_parent_directories=True)
sha = repo.head.object.hexsha

Something to consider when using this library. The following is taken from gitpython.readthedocs.io

Leakage of System Resources

GitPython is not suited for long-running processes (like daemons) as it tends to leak system resources. It was written in a time where destructors (as implemented in the __del__ method) still ran deterministically.

In case you still want to use it in such a context, you will want to search the codebase for __del__ implementations and call these yourself when you see fit.

Another way assure proper cleanup of resources is to factor out GitPython into a separate process which can be dropped periodically

Answer #2

In Python, functions are first-class objects, this means: function references can be passed in inputs to other functions and/or methods, and executed from inside them.

Instances of Classes (aka Objects), can be treated as if they were functions: pass them to other methods/functions and call them. In order to achieve this, the __call__ class function has to be specialized.

def __call__(self, [args ...]) It takes as an input a variable number of arguments. Assuming x being an instance of the Class X, x.__call__(1, 2) is analogous to calling x(1,2) or the instance itself as a function.

In Python, __init__() is properly defined as Class Constructor (as well as __del__() is the Class Destructor). Therefore, there is a net distinction between __init__() and __call__(): the first builds an instance of Class up, the second makes such instance callable as a function would be without impacting the lifecycle of the object itself (i.e. __call__ does not impact the construction/destruction lifecycle) but it can modify its internal state (as shown below).

Example.

class Stuff(object):

    def __init__(self, x, y, range):
        super(Stuff, self).__init__()
        self.x = x
        self.y = y
        self.range = range

    def __call__(self, x, y):
        self.x = x
        self.y = y
        print "__call__ with (%d,%d)" % (self.x, self.y)

    def __del__(self):
        del self.x
        del self.y
        del self.range

>>> s = Stuff(1, 2, 3)
>>> s.x
1
>>> s(7, 8)
__call__ with (7,8)
>>> s.x
7

Answer #3

You should use the print() function which is available since Python 2.6+

from __future__ import print_function  # Only needed for Python 2
print("hi there", file=f)

For Python 3 you don"t need the import, since the print() function is the default.

The alternative would be to use:

f = open("myfile", "w")
f.write("hi there
")  # python will convert 
 to os.linesep
f.close()  # you can omit in most cases as the destructor will call it

Quoting from Python documentation regarding newlines:

On output, if newline is None, any " " characters written are translated to the system default line separator, os.linesep. If newline is "", no translation takes place. If newline is any of the other legal values, any " " characters written are translated to the given string.

Answer #4

__del__ is a finalizer. It is called when an object is garbage collected which happens at some point after all references to the object have been deleted.

In a simple case this could be right after you say del x or, if x is a local variable, after the function ends. In particular, unless there are circular references, CPython (the standard Python implementation) will garbage collect immediately.

However, this is an implementation detail of CPython. 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.

Even more, variables can live for a long time for many reasons, e.g. a propagating exception or module introspection can keep variable reference count greater than 0. Also, variable can be a part of cycle of references — CPython with garbage collection turned on breaks most, but not all, such cycles, and even then only periodically.

Since you have no guarantee it"s executed, one should never put the code that you need to be run into __del__() — instead, this code belongs to finally clause of the try block or to a context manager in a with statement. However, there are valid use cases for __del__: e.g. if an object X references Y and also keeps a copy of Y reference in a global cache (cache["X -> Y"] = Y) then it would be polite for X.__del__ to also delete the cache entry.

If you know that the destructor provides (in violation of the above guideline) a required cleanup, you might want to call it directly, since there is nothing special about it as a method: x.__del__(). Obviously, you should only do so if you know it can be called twice. Or, as a last resort, you can redefine this method using

type(x).__del__ = my_safe_cleanup_method

Answer #5

The crucial distinction between Python"s __init__ and those other languages constructors is that __init__ is not a constructor: it"s an initializer (the actual constructor (if any, but, see later;-) is __new__ and works completely differently again). While constructing all superclasses (and, no doubt, doing so "before" you continue constructing downwards) is obviously part of saying you"re constructing a subclass"s instance, that is clearly not the case for initializing, since there are many use cases in which superclasses" initialization needs to be skipped, altered, controlled -- happening, if at all, "in the middle" of the subclass initialization, and so forth.

Basically, super-class delegation of the initializer is not automatic in Python for exactly the same reasons such delegation is also not automatic for any other methods -- and note that those "other languages" don"t do automatic super-class delegation for any other method either... just for the constructor (and if applicable, destructor), which, as I mentioned, is not what Python"s __init__ is. (Behavior of __new__ is also quite peculiar, though really not directly related to your question, since __new__ is such a peculiar constructor that it doesn"t actually necessarily need to construct anything -- could perfectly well return an existing instance, or even a non-instance... clearly Python offers you a lot more control of the mechanics than the "other languages" you have in mind, which also includes having no automatic delegation in __new__ itself!-).

Tutorials