With statement, auto-delete object
Asked Answered
R

3

6

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__?

Rectum answered 28/9, 2018 at 8:39 Comment(1)
The with does not define a specific scope, so you can use variables after these have been defined.Elburt
T
2
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'
Trio answered 28/9, 2018 at 8:45 Comment(3)
I see, it actually makes sense that you have to be "outside" of the object to delete itMetalloid
I just noticed one can even delete methods in exit, this can be usefulMetalloid
well, not delete, but disable access by doing: self.my_method = NoneMetalloid
P
5

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).

Poleaxe answered 28/9, 2018 at 8:41 Comment(2)
yes, but isn't it possible to do it in the exit? it would be safer for my application if the user could open it only with a "with" and not have access afterwardMetalloid
@RémiBaudoux: you can change the state of your object, such that it is no longer "accessible" (like the file handler does, one out of the with statement, the file handler is closed, but the variable still exists).Elburt
E
4

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.

Elburt answered 28/9, 2018 at 9:13 Comment(2)
Thank you, for the accuracy! I think I will delete all the variables and methods in the exit, such as it raises an error by itself if I try to access whatever in the class.Metalloid
@RémiBaudoux But be aware that this isn't very Pythonic. The next developer (or user of your library) might be very surprised at this behavior.Poleaxe
T
2
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'
Trio answered 28/9, 2018 at 8:45 Comment(3)
I see, it actually makes sense that you have to be "outside" of the object to delete itMetalloid
I just noticed one can even delete methods in exit, this can be usefulMetalloid
well, not delete, but disable access by doing: self.my_method = NoneMetalloid

© 2022 - 2024 — McMap. All rights reserved.