For testing purposes, I'm creating temporary classes which I want to delete (before other test methods run). Trouble is, [superclass].__subclasses__()
still lists the deleted classes, even after running garbage collection.
Here's what my test method looks like:
class Apple(Fruit):
@staticmethod
def mass(size):
return size
class Orange(Fruit):
@staticmethod
def mass(size):
return size
try:
Apple()
Orange()
a1 = Apple(type='fuji')
finally:
if 'a1' in locals():
print 'del a1'
del a1
print gc.get_referrers(Apple)
print gc.get_referrers(Orange)
del Apple
del Orange
print Fruit.__subclasses__()
gc.collect()
print Fruit.__subclasses__()
The output is as follows:
del a1
[<frame object at 0xabcdef0>, (<class 'Apple'>, <class 'Fruit'>, <type 'object'>), <Apple object at 0x4443331>, {'a1': <Apple object at 0x4443331, 'self': <FruitTests testMethod=test_pass_Fruit_core>, 'Orange': <class 'Orange'>, 'Apple': <class 'Apple'>}]
[<frame object at 0xabcdef0>, (<class 'Orange'>, <class 'Fruit'>, <type 'object'>), {'a1': <Apple object at 0x4443331, 'self': <FruitTests testMethod=test_pass_Fruit_core>, 'Orange': <class 'Orange'>, 'Apple': <class 'Apple'>}]
[<class 'Apple'>, <class 'Orange'>]
[<class 'Apple'>, <class 'Orange'>]
None of the classes involved have an explicitly-defined __del__()
, although Fruit
does use __metaclass__ = abc.ABCMeta
and a @abc.abstractmethod
decorator on Fruit.mass()
.
The remaining class reference has something to do with the assignment of the Fruit
instance to a variable: If I remove all the lines containing a1
, the final Fruit.__subclasses__()
returns []
- even though the bare constructor Apple()
still runs.
This is a problem for me because another test is concerned with fruit interactions (call the relevant method-to-be-tested blends()
), and that uses a Fruit.__subclasses__()
call to check combinations of different types of Fruit
. I haven't bothered to define interactions with these test classes, and that's confusing blends()
.
Any hints on why these references are sticking around would be appreciated.
Edit: If I call gc.get_referrers(Apple) after gc.collect(), I get an "UnboundLocalError: local variable 'Apple' referenced before assignment" Fruit defines a number of methods with the "@classmethod "and "@property" decorators, and references another class which handles "blends()"...
After garbage collection, gc.get_referrers(Fruit.__subclasses__()[0])
returns
[{'a1': <Apple object at 0x4443331>, 'self': <FruitTests testMethod=test_pass_Fruit_core>, 'Orange': <class 'Orange'>, 'Apple': <class 'Apple'>}, <Apple object at 0x4443331>, (<class 'Apple'>, <class 'Fruit'>, <type 'object'>)]
Edit: The problem occurs when I run just this one test method. (It also occurs when I queue up multiple tests.) I tried rebooting my IDE (PyCharm) and running "./manage.py test FruitTests.test_pass_Fruit_core " from the command line. All cases yield the same results, although the particular memory addresses vary. locals() is being called directly - I don't have it aliased anywhere.
Edit: The entire module defining Fruit:
from abc import abstractmethod, ABCMeta
class Fruit(object):
__metaclass__ = ABCMeta
def __init__(self, **kwargs):
super(Fruit, self).__init__()
@abstractmethod
def mass(self, size):
raise NotImplementedError
In the test method, test_pass_Fruit_core(), "a1 = Apple()" and "a1 = Apple(type='fuji')" produce the same results. Dropping the assignment to "a1" makes no difference, but if I drop the call to "locals()", garbage collection works as expected - Apple is no longer available as a subclass of Fruit at the end of the method.
The persistent reference is being created in the call to locals(). To guarantee "del a1" does not generate an error if one was created inside the "try:" block, assign "a1 = None" before the block and skip the call to locals().
Final, working code follows. Compare with the first code block above: class Apple(Fruit): @staticmethod def mass(size): return size
class Orange(Fruit):
@staticmethod
def mass(size):
return size
a1 = None
try:
Apple()
Orange()
a1 = Apple(type='fuji')
finally:
del a1
print gc.get_referrers(Apple)
print gc.get_referrers(Orange)
del Apple
del Orange
print Fruit.__subclasses__()
gc.collect()
sc = Fruit.__subclasses__()
print sc
if len(sc) > 0:
print 42, gc.get_referrers(sc[0])