bup
bup

Reputation: 113

Immediately after class definition is complete, where are all of the references to the class object located?

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.

Upvotes: 4

Views: 84

Answers (1)

user2357112
user2357112

Reputation: 280733

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

Upvotes: 3

Related Questions