How to make super() work by manually filling the __class__ cell? How to make super() work by manually filling the __class__ cell? python-3.x python-3.x

How to make super() work by manually filling the __class__ cell?


Seriously: you really don't want to do this.

But, it's useful for advanced users of Python to understand this, so I'll explain it.

Cells and freevars are the values assigned when a closure is created. For example,

def f():    a = 1    def func():        print(a)    return func

f returns a closure based on func, storing a reference to a. That reference is stored in a cell (actually in a freevar, but which one is up to the implementation). You can examine this:

myfunc = f()# ('a',)print(myfunc.__code__.co_freevars)# (<cell at 0xb7abce84: int object at 0x82b1de0>,)print(myfunc.__closure__)

("cells" and "freevars" are very similar. Freevars have names, where cells have indexes. They're both stored in func.__closure__, with cells coming first. We only care about freevars here, since that's what __class__ is.)

Once you understand that, you can see how super() actually works. Any function that contains a call to super is actually a closure, with a freevar named __class__ (which is also added if you refer to __class__ yourself):

class foo:    def bar(self):        print(__class__)

(Warning: this is where things get evil.)

These cells are visible in func.__closure__, but it's read-only; you can't change it. The only way to change it is to create a new function, which is done with the types.FunctionType constructor. However, your __init__ function doesn't have a __class__ freevar at all--so we need to add one. That means we have to create a new code object as well.

The below code does this. I added a base class B for demonstrative purposes. This code makes some assumptions, eg. that __init__ doesn't already have a free variable named __class__.

There's another hack here: there doesn't seem to be a constructor for the cell type. To work around that, a dummy function C.dummy is created which has the cell variable we need.

import typesclass B(object):    def __init__(self):        print("base")class C(B):    def dummy(self): __class__def __init__(self):    print('calling __init__')    super().__init__()def MakeCodeObjectWithClass(c):    """    Return a copy of the code object c, with __class__ added to the end    of co_freevars.    """    return types.CodeType(c.co_argcount, c.co_kwonlyargcount, c.co_nlocals,            c.co_stacksize, c.co_flags, c.co_code, c.co_consts, c.co_names,            c.co_varnames, c.co_filename, c.co_name, c.co_firstlineno,            c.co_lnotab, c.co_freevars + ('__class__',), c.co_cellvars)new_code = MakeCodeObjectWithClass(__init__.__code__)old_closure = __init__.__closure__ or ()C.__init__ = types.FunctionType(new_code, globals(), __init__.__name__,    __init__.__defaults__, old_closure + (C.dummy.__closure__[0],))if __name__ == '__main__':    c = C()


You can use the function's dictionary.

def f(self):    super(f.owner_cls, self).f()    print("B")def add_to_class(cls, member, name=None):    if hasattr(member, 'owner_cls'):        raise ValueError("%r already added to class %r" % (member, member.owner_cls))    member.owner_cls = cls    if name is None:        name = member.__name__    setattr(cls, name, member)class A:     def f(self):         print("A")class B(A):     passadd_to_class(B, f)B().f()

You can even add another attribute member_name if you don't want to hardcode the name of the name of the member inside the function.


Maybe, but by would you? In both cases you need to somehow be explicit of which class it is, because the implicit way didn't work. Maybe you can set the cell explicitly somehow, but there is no reason to do that. Just pass in the parameters explicitly.

def __init__(self):    print('calling __init__')    super(self.__class__, self).__init__()class C(object):    __init__ = __init__if __name__ == '__main__':    c = C()

(It's better if you can pass in the actual class directly, like so:

def __init__(self):    print('calling __init__')    super(C, self).__init__()class C(object):    __init__ = __init__if __name__ == '__main__':    c = C()

But if you can that, you could put the __init__ on C directly, so assume you can't.