15

I've got a class that wraps some file handling functionality I need. Another class creates an instance of the filehandler and uses it for an indeterminate amount of time. Eventually, the caller is destroyed, which destroys the only reference to the filehandler.

What is the best way to have the filehandler close the file?

I currently use __del__(self) but after seeing several different questions and articles, I'm under the impression this is considered a bad thing.

class fileHandler:
    def __init__(self, dbf):
        self.logger = logging.getLogger('fileHandler')
        self.thefile = open(dbf, 'rb')
    def __del__(self):
        self.thefile.close()

That's the relevent bit of the handler. The whole point of the class is to abstract away details of working with the underlying file object, and also to avoid reading the entire file into memory unnecessarily. However, part of handling the underlying file is closing it when the object falls out of scope.

The caller is not supposed to know or care about the details involved in the filehandler. It is the filehandler's job to release any necessary resources involved when it falls out of scope. That's one of the reasons it was abstracted in the first place. So, I seem to be faced with moving the filehandler code into the calling object, or dealing with a leaky abstraction.

Thoughts?

Community
  • 1
  • 1
Spencer Rathbun
  • 14,510
  • 6
  • 54
  • 73

2 Answers2

24

__del__ is not, by itself, a bad thing. You just have to be extra careful to not create reference cycles in objects that have __del__ defined. If you do find yourself needing to create cycles (parent refers to child which refers back to parent) then you will want to use the weakref module.

So, __del__ is okay, just be wary of cylic references.

Garbage collection: The important point here is that when an object goes out of scope, it can be garbage collected, and in fact, it will be garbage collected... but when? There is no guarantee on the when, and different Python implementations have different characteristics in this area. So for managing resources, you are better off being explicit and either adding .close() on your filehandler or, if your usage is compatible, adding __enter__ and __exit__ methods.

The __enter__ and __exit__ methods are described here. One really nice thing about them is that __exit__ is called even when exceptions occur, so you can count or your resources being closed gracefully.

Your code, enhanced for __enter__/__exit__:

class fileHandler:
    def __init__(self, dbf):
        self.logger = logging.getLogger('fileHandler')
        self.thefilename = dbf
    def __enter__(self):
        self.thefile = open(self.thefilename, 'rb')
        return self
    def __exit__(self, *args):
        self.thefile.close()

Note that the file is being opened in __enter__ instead of __init__ -- this allows you to create the filehandler object once, and then use it whenever you need to in a with without recreating it:

fh = filehandler('some_dbf')
with fh:
    #file is now opened
    #do some stuff
#file is now closed
#blah blah
#need the file again, so
with fh:
    # file is open again, do some stuff with it
#etc, etc 
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • Nice trick with opening the file in `__enter__()` -- you could also do some magic in there to retain the file position between opens/closes. – kindall Dec 20 '11 at 23:07
  • 1
    Very nice, this is much closer to what I wanted. The ref cycles is the first thing I checked for, and I don't have any. But some articles say that whole object chains will be noncollectable if a leaf object has a del method, regardless of ref cycles. – Spencer Rathbun Dec 21 '11 at 13:37
  • @SpencerRathbun: All the documentation I have been able to find states the items are uncollectible **only if** there are ref cycles. – Ethan Furman Dec 21 '11 at 19:46
  • While it isn't exactly what I wanted, it'll do the job. I'm accepting your answer. – Spencer Rathbun Dec 21 '11 at 20:05
  • 1
    @SpencerRathbun: I omitted adding `.close`,`open`, and `__del__` methods as it would have complicated my example (and I thought opening the file in `__enter__` was a cool trick ;) but you can certainly add them back in, and then you can use the `filehandler` in whichever way makes sense at the time. Keep in mind, though, the lack of time gaurantees when using `__del__`. – Ethan Furman Dec 21 '11 at 20:13
  • Why do you have *args in __exit__(self, *args):? – Niek de Klein Feb 20 '12 at 14:18
  • 1
    @NiekdeKlein: Because `__exit__` actually takes three other arguments (Exception info if one occured, or None three times) but I don't need them named since the code will not be doing anything different: possibility 1) exception occured -- result -> close the file; possibility 2) no exception occured -- result -> close the file. – Ethan Furman Feb 22 '12 at 20:31
8

As you've written it the class doesn't make the file close any more reliably. If you simple drop the filehandler instance on the floor then the file won't close until the object is destroyed. This might be immediately or might not be until the object is garbage collected, but just dropping a plain file object on the floor would close it just as quickly. If the only reference to thefile is from inside your class object then when filehandler is garbage collected thefile will be also be garbage collected and therefore closed at the same time.

The correct way to use files is to use the with statement:

with open(dbf, 'rb') as thefile:
    do_something_with(thefile)

that will guarantee that thefile is always closed whenever the with clause exits. If you want to wrap your file inside another object you can do that too by defining __enter__ and __exit__ methods:

class FileHandler:
    def __init__(self, dbf):
        self.logger = logging.getLogger('fileHandler')
        self.thefile = open(dbf, 'rb')
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_value, traceback):
        self.thefile.close()

and then you can do:

with FileHandler(dbf) as fh:
    do_something_with(fh)

and be sure the file gets closed promptly.

Zorawar
  • 6,505
  • 2
  • 23
  • 41
Duncan
  • 92,073
  • 11
  • 122
  • 156