With statement, auto-delete object

683 views Asked by At

Is it possible to delete an object form inside its class?

class A():
    def __init__(self):
        print("init")
        self.b="c"
    def __enter__(self):
        print("enter")
        return self
    def __exit__(self, type, value, traceback):
        print("exit")      

with A() as a:
    print(a.b)
print(a.b)

returns:

init
enter
c
exit
c

How comes I still have access to the a object after exiting the with ? Is there a way to auto-delete the object in __exit__?

3

There are 3 answers

3
SpringMaple On BEST ANSWER
class A():
    def __init__(self):
        print("init")
        self.b="c"
    def __enter__(self):
        print("enter")
        return self
    def __exit__(self, type, value, traceback):
        print("exit") 
        del self.b

with A() as a:
    print(a.b)
print(a.b)

You can't delete instance of class A itself within __exit__. The best you can do is delete the property b.

init
enter
c
exit
Traceback (most recent call last):
  File "main.py", line 14, in <module>
    print(a.b)
AttributeError: A instance has no attribute 'b'
2
Alfe On

Yes and no. Use del a after the with clause. This will remove the variable a who is the last reference holder on the object.

The object itself (i. e. in __exit__()) cannot make the ones who know about it and hold a reference (i. e. the code at the with clause) forget this. As long as the reference exists, the object will exist.

Of course, your object can empty itself in the __exit__() and remain as a hollow thing (e. g. by del self.b in this case).

2
willeM_ Van Onsem On

Short answer: it is (to some extent) possible, but not advisable at all.

The with part in Python has no dedicated scope, so that means that variables defined in the with statement are not removed. This is frequently wanted behavior. For example if you load a file, you can write it like:

with open('foo.txt') as f:
    data = list(f)

print(data)

You do not want to remove the data variable: the with is here used to ensure that the file handler is properly closed (and the handler is also closed if an exception occurs in the body of the with).

Strictly speaking you can delete local variables that refer to the A() object, by a "hackish" solution: we inspect the call stack, and remove references to self (or another object), like:

import inspect

class A(object):

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        locs = inspect.stack()[1][0].f_locals
        ks = [k for k, v in locs.items() if v is self]
        for k in ks:
            del locs[k]

Then it will delete it like:

>>> with A() as a:
...   pass
...
>>> a
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined 

But I would strongly advice against that. First of all, if the variabe is global, or located outside the local scope, it will not get removed here (we can fix this, but it will introduce a lot of extra logic).

Furthermore it is not said that the variable even exists, if the variable is iterable, one can define it like:

# If A.__enter__ returns an iterable with two elements

with A() as (foo, bar):
    pass

So then these elements will not get recycled. Finally if the __enter__ returns self, it is possible that it "removes too much", since one could write with foo as bar, and then both foo and bar will be removed.

Most IDEs will probably not be able to understand the logic in the __exit__, anyway, and hence still will include a in the autocompletion.

In general, it is better to simply mark the object as closed, like:

import inspect

class A(object):

    def __init__(self):
        self.closed = False

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.closed = True

    def some_method(self):
        if self.closed:
            raise Exception('A object is closed')
        # process request

The above is also the way it is handled for a file handler.