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

In Python 3, you can use super() instead of super(MyClass, self) , but this only works on methods defined inside the class. As described in Michele Simionato's article, the following example does not work:

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

It __class__ because super() looking for a __class__ cell that is not defined in this case.

Is it possible to set this cell manually after the function is defined or is it impossible?

Unfortunately, I donโ€™t understand how cells work in this context (a lot of documentation was not found for this). I hope for something like

 __init__.__class_cell_thingy__ = C 

Of course, I would use this only in a situation where the class assignment is unique / unique (the whole process of adding methods to the in class is automated in my case, so it would be easy to add such a line).

+8
python super metaprogramming
source share
3 answers
Seriously: you really don't want to do this.

But, it is useful for experienced Python users to understand this, so I will explain it.

Cells and freevars are the values โ€‹โ€‹assigned when creating the closure. For example,

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

f returns a closure based on func , keeping a reference to a . This link is stored in the cell (actually in freevar, but which corresponds to the implementation). You can check this out:

 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 the cells have indexes. Both of them are stored in func.__closure__ , and the cells go first. Here we only care about freevars , since there are __class__ .)

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

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

(Warning: this is where things get evil.)

These cells are visible in func.__closure__ , but read-only; you cannot change it. The only way to change it is to create a new function that is executed using the types.FunctionType constructor. However, your __init__ function does not have __class__ freevar __class__ all - so we need to add it. This means that we also need to create a new code object.

Below is the code. I added base class B for demo purposes. This code makes some assumptions, for example. that __init__ no longer has a free variable named __class__ .

There is another hack: there is no constructor for the cell type. To get around this, a dummy C.dummy function is C.dummy that has the required cell variable.

 import types class 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() 
+15
source share

You can use the function 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): pass add_to_class(B, f) B().f() 

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

+2
source share
Maybe, but, right? In both cases, you need to somehow be explicit what class it is, because the implicit way does not work. Perhaps you can somehow set the cell, but there is no reason for this. Just pass the parameters explicitly.
 def __init__(self): print('calling __init__') super(self.__class__, self).__init__() class C(object): __init__ = __init__ if __name__ == '__main__': c = C() 

(Better if you can directly pass the actual class, for example:

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

But if you can do this, you can directly put __init__ in C , so suppose you can't.

+1
source share

All Articles