Four references to a class are produced when a class is created which that never had a __classcell__ passed in the namespace argument of type.__new__, aka a class with no functions that ever need the opcode LOAD_NAME - __class__. I can account for one, but since I can barely read C I can only speculate about the other 3. Most obviously, the first is the reference in the __dict__ of the module the class was defined in. Another I think one is in a cache in typeobject.c. After a while if the reference count of a class drops below 4 it is destroyed, which is a good thing for classes defined in in function since there's no way to delete it after the function has been executed. 
Also there is perhaps a reference in whatever type.mro uses to generate mros. 
Finally, I'm guessing the last is somewhere zero argument super can access, and that is the real info I'm after: why does a function created outside a class definition not work with zero argument super, even when it has '__class__' in its __code__.co_names and a cell with the class in its __closure__? A RuntimeError: super(): __class__ cell not found is still produced.
Classes that had a __classcell__ (I say had because I believe that name is eventually deleted) have 5 references upon creation, one more than not. Seeing as the cell containing the class object in the __closure__ of all functions that mention zero argument super() or __class__ are identical I think it's a good bet that the additional reference is used by the types.FunctionType.__closure__ descriptor to produce the tuple containing the cells.
Let's take a look with gc.get_referrers:
>>> import gc
>>> import pprint
>>> class Foo: pass
... 
>>> pprint.pprint(gc.get_referrers(Foo))
[<attribute '__dict__' of 'Foo' objects>,
 <attribute '__weakref__' of 'Foo' objects>,
 (<class '__main__.Foo'>, <class 'object'>),
 {'Foo': <class '__main__.Foo'>,
  '__annotations__': {},
  '__builtins__': <module 'builtins' (built-in)>,
  '__cached__': None,
  '__doc__': None,
  '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7ff284402da0>,
  '__name__': '__main__',
  '__package__': None,
  '__spec__': None,
  'gc': <module 'gc' (built-in)>,
  'pprint': <module 'pprint' from '/usr/local/lib/python3.6/pprint.py'>}]
We can see in this list that the 3 references you were missing are from the __dict__ and __weakref__ descriptors and the class's __mro__. (It might not be obvious that the third entry is the __mro__, but we can confirm:)
>>> gc.get_referrers(Foo)[2] is Foo.__mro__
True
The __dict__ and __weakref__ descriptors manage access to the __dict__ and __weakref__ attributes of Foo instances, and they need a reference to Foo for type checking, to make sure they're only used on Foo instances.
The __mro__ is the sequence of classes Python searches to resolve class attributes of Foo, and to resolve instance attributes of Foo which are managed by a data descriptor or which don't have an entry in the instance __dict__.
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