I am using Python 2.7 and trying to have a clean memory (as I am writing a small server). I am facing an issue where the object of the last raise are still kept in the garbage collector (and then __ del __ is not called after the first try/except).
Here is a small example:
import gc
class A(object):
def raiser(self):
0/0 # will raise an exception
a = A()
try:
a.raiser()
except:
pass
a = None # should release the object from memory
gc.collect() # just to be sure, but doesn't do anything
print '1. Nbr of instance of A in gc : '
print len([o for o in gc.get_objects() if isinstance(o, A)]) # get 1 but expected 1
try:
0/0
except:
pass
print '2. Nbr of instance of A in gc : '
print len([o for o in gc.get_objects() if isinstance(o, A)]) # get 0 (finally)
and this returns:
1. Nbr of instance of A in gc :
1
2. Nbr of instance of A in gc :
0
while I was waiting to have 0 for both. Where Is the A instance stored ?
Thanks a lot Alex
That instance is stored (at least) in the interrupted frame of the raiser
function, as we can check using gc.get_referrers
:
import gc
import inspect
class A(object):
def raiser(self):
print inspect.currentframe()
0/0
a = A()
try:
a.raiser()
except:
pass
a = None # should release the object from memory
gc.collect() # just to be sure, but doesn't do anything
print 'b. Nbr of instance of A in gc : '
print [map(lambda s: str(s)[:64], gc.get_referrers(o)) for o in gc.get_objects() if isinstance(o, A)]
try:
0/0
except:
pass
print '---'
print 'c. Nbr of instance of A in gc : '
print [map(lambda s: str(s)[:64], gc.get_referrers(o)) for o in gc.get_objects() if isinstance(o, A)]
This prints:
<frame object at 0x239fa70>
---
b. Nbr of instance of A in gc :
[["[[], ('Return a new Arguments object replacing specified fields ",
"{'A': <class '__main__.A'>, 'a': None, '__builtins__': <module '",
'<frame object at 0x239fa70>']]
---
c. Nbr of instance of A in gc :
[]
Note the last object is the same as the frame of raiser
. This also means you'll get the same result too if you just write
try:
A().raiser()
except:
pass
We could do the same trick again to see what is holding the frame object:
class A(object):
def raiser(self):
0/0
try:
A().raiser()
except:
pass
print [(map(lambda x: str(x)[:64], gc.get_referrers(o)), # Print the referrers
map(type, gc.get_referents(o))) # Check if it's the frame holding an A
for o in gc.get_objects()
if inspect.isframe(o)]
The result is
[(['<traceback object at 0x7f07774a3bd8>',
'[<function getblock at 0x7f0777462cf8>, <function getsourcelines',
"{'A': <class '__main__.A'>, '__builtins__': <module '__builtin__"
], [<type 'frame'>, <type 'code'>, <type 'dict'>, <type 'dict'>,
<class '__main__.A'>]),
(['<frame object at 0xe3d3c0>',
'<traceback object at 0x7f07774a3f38>',
'[<function getblock at 0x7f0777462cf8>, <function getsourcelines',
"{'A': <class '__main__.A'>, '__builtins__': <module '__builtin__"
], [<type 'code'>, <type 'dict'>, <type 'dict'>, <type 'dict'>,
<type 'NoneType'>])]
So we see that the frame is at least held by a traceback
object. We can find info about the traceback
object in the traceback
module, which mentions:
The module uses traceback objects — this is the object type that is stored in the variables
sys.exc_traceback
(deprecated) andsys.last_traceback
and returned as the third item fromsys.exc_info()
.
Which means these sys variables may be the one that is holding the frame alive. Indeed, if we call sys.exc_clear()
to clear the exception info, the instance will be deallocated:
import gc
import sys
class A(object):
def raiser(self):
0/0
try:
A().raiser()
except:
pass
print len([o for o in gc.get_objects() if isinstance(o, A)]) # prints 1
sys.exc_clear()
print len([o for o in gc.get_objects() if isinstance(o, A)]) # prints 0
After a little investigation. If you import sys
and put
...
a = None
print sys.exc_info()
#sys.exc_clear() # if you add this your A instance will get gc as expected
gc.collect()
...
you'll notice that the interpreter still holds a reference to the ZeroDivisionError
trown inside try...catch
even though the code is already outside. Since exception hold references to frames where they are thrown (if only to print traceback), your A instance still has non-zero refcount.
Once another exception is thrown (and handled) interpreter drops the reference to the first one and collects both the exception and objects connected to it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With