views:

255

answers:

2

Background: I'm doing COM programming of National Instruments' TestStand in Python. TestStand complains if objects aren't "released" properly (it pops up an "objects not released properly" debug dialog box). The way to release the TestStand COM objects in Python is to ensure all variables no longer contain the object—e.g. del() them, or set them to None. Or, as long as the variables are function local variables, the object is released as soon as the variable goes out of scope when the function ends.

Well, I've followed this rule in my program, and my program releases object properly as long as there are no exceptions. But if I get an exception, then I'm getting the "objects not released" message from TestStand. This seems to indicate that function local variables aren't going out of scope normally, when an exception happens.

Here is a simplified code example:

class TestObject(object):
    def __init__(self, name):
        self.name = name
        print("Init " + self.name)
    def __del__(self):
        print("Del " + self.name)

def test_func(parameter):
    local_variable = parameter
    try:
        pass
#        raise Exception("Test exception")
    finally:
        pass
#        local_variable = None
#        parameter = None

outer_object = TestObject('outer_object')
try:
    inner_object = TestObject('inner_object')
    try:
        test_func(inner_object)
    finally:
        inner_object = None
finally:
    outer_object = None

When this runs as shown, it shows what I expect:

Init outer_object
Init inner_object
Del inner_object
Del outer_object

But if I uncomment the raise Exception... line, instead I get:

Init outer_object
Init inner_object
Del outer_object
Traceback (most recent call last):
...
Exception: Test exception
Del inner_object

The inner_object is deleted late due to the exception.

If I uncomment the lines that set both parameter and local_variable to None, then I get what I expect:

Init outer_object
Init inner_object
Del inner_object
Del outer_object
Traceback (most recent call last):
...
Exception: Test exception

So when exceptions happen in Python, what exactly happens to function local variables? Are they being saved somewhere so they don't go out of scope as normal? What is "the right way" to control this behaviour?

A: 

A function's scope is for the entire function. Handle this in finally.

Ignacio Vazquez-Abrams
+1  A: 

Your exception-handling is probably creating reference loops by keeping references to frames. As the docs put it:

Note Keeping references to frame objects, as found in the first element of the frame records these functions return [[NB: "these functions" here refers to some in module inspect, but the rest of the paragraph applies more widely!]], can cause your program to create reference cycles. Once a reference cycle has been created, the lifespan of all objects which can be accessed from the objects which form the cycle can become much longer even if Python’s optional cycle detector is enabled. If such cycles must be created, it is important to ensure they are explicitly broken to avoid the delayed destruction of objects and increased memory consumption which occurs. Though the cycle detector will catch these, destruction of the frames (and local variables) can be made deterministic by removing the cycle in a finally clause. This is also important if the cycle detector was disabled when Python was compiled or using gc.disable(). For example:

def handle_stackframe_without_leak():
    frame = inspect.currentframe()
    try:
        # do something with the frame
    finally:
        del frame
Alex Martelli
Thanks. I'm not quite sure I understand "Your exception-handling is probably creating reference loops by keeping references to frames." So I've added a code example. I'd be interested to know what you think of it.
Craig McQueen
@Craig, no reference loops, but still longer-lived references than you might like, because all the frames in the stack (and therefore their references to objects) must survive as long as the exception is being dealt with (when you set to None all those references, then objects can get removed even though the frames still survive).
Alex Martelli