'How to pass function with super() when creating class dynamically?
Let's say I have this code:
class StaticParent:
def _print(self):
print("I'm StaticParent")
class StaticChild(StaticParent):
def _print(self):
print('StaticChild saying: ')
super()._print()
def _parent_print_proto(self):
print("I'm DynamicParent")
def _child_print_proto(self):
print('DynamicChild saying: ')
super()._print()
DynamicParent = type('DynamicParent', tuple([]), {"_print": _parent_print_proto})
DynamicChild = type('DynamicChild', (DynamicParent,), {"_print": _child_print_proto})
sc = StaticChild()
sc._print()
dc = DynamicChild()
dc._print()
And it's output is:
StaticChild saying:
I'm StaticParent
DynamicChild saying:
Traceback (most recent call last):
File "/tmp/example.py", line 28, in <module>
dc._print()
File "/tmp/example.py", line 17, in _child_print_proto
super()._print()
RuntimeError: super(): __class__ cell not found
So question is how to create prototype method for many classes that calling super()?
PS I tried to implement method with lambda but it is also not working:
DynamicChildWithLambda = type('DynamicChild', (DynamicParent,), {"_print": lambda self : print('Lambda saying: ', super()._print())})
Traceback (most recent call last):
File "/tmp/example.py", line 30, in <module>
dcwl._print()
File "/tmp/example.py", line 23, in <lambda>
DynamicChildWithLambda = type('DynamicChild', (DynamicParent,), {"_print": lambda self : print('Lambda saying: ', super()._print())})
RuntimeError: super(): __class__ cell not found
PS2 Also I tried this way:
class StaticParent:
def _print(self):
print("I'm StaticParent")
def _child_print_proto(self):
print('DynamicChild saying: ')
super(StaticParent, self)._print()
DynamicChild = type('DynamicChild', (StaticParent,), {"_print": _child_print_proto})
dc = DynamicChild()
dc._print()
DynamicChild saying:
Traceback (most recent call last):
File "/tmp/example.py", line 13, in <module>
dc._print()
File "/tmp/example.py", line 8, in _child_print_proto
super(StaticParent, self)._print()
AttributeError: 'super' object has no attribute '_print'
Solution 1:[1]
Methods defined within a class get a fake closure scope that automatically provides the class it was defined in to no-arg super(). When defined outside a class, it can't do this (because clearly no class is being defined at the time you define the method). But you can still make a closure the old-fashioned way, by actually writing a closure function that you manually define __class__ appropriately in:
class StaticParent:
def _print(self):
print("I'm StaticParent")
class StaticChild(StaticParent):
def _print(self):
print('StaticChild saying: ')
super()._print()
def _parent_print_proto(self):
print("I'm DynamicParent")
# Nesting allows us to make the inner function have an appropriate __class__
# defined for use by no-arg super
def _make_child_print_proto(cls):
__class__ = cls
def _child_print_proto(self):
print('DynamicChild saying: ')
super()._print()
return _child_print_proto
DynamicParent = type('DynamicParent', tuple([]), {"_print": _parent_print_proto})
DynamicChild = type('DynamicChild', (DynamicParent,), {})
# Need DynamicChild to exist to use it as __class__, bind after creation
DynamicChild._print = _make_child_print_proto(DynamicChild)
sc = StaticChild()
sc._print()
dc = DynamicChild()
dc._print()
Yes, it's hacky and awful. In real code, I'd just use the less common explicit two-arg super:
def _child_print_proto(self):
print('DynamicChild saying: ')
super(DynamicChild, self)._print()
That's all super() does anyway; Python hides the class it was defined in in closure scope as __class__, super() pulls it and the first positional argument, assumed to be self, and implicitly does the same thing the two-arg form did explicitly.
To be clear: You cannot pass self.__class__ manually as the first argument to two-arg super(), to simulate the __class__ that is bound in closure scope. It appears to work, and it does work, until you actually make a child of the class with that method and try to call the method on it (and the whole point of super() is you might have an arbitrarily complex class hierarchy to navigate; you can't just say "Oh, but my class is special enough to never be subclassed again"). If you do something as simple as adding:
class DynamicGrandChild(DynamicChild):
pass
dgc = DynamicGrandChild()
dgc._print()
to the self.__class__-using code from Epsi95's answer, you're going to see:
StaticChild saying:
I'm StaticParent
DynamicChild saying:
I'm DynamicParent
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
DynamicChild saying:
... repeats a thousand times or so ...
DynamicChild saying:
DynamicChild saying:
Traceback (most recent call last):
File ".code.tio", line 31, in <module>
dgc._print()
File ".code.tio", line 15, in _child_print_proto
super(self.__class__, self)._print()
File ".code.tio", line 15, in _child_print_proto
super(self.__class__, self)._print()
File ".code.tio", line 15, in _child_print_proto
super(self.__class__, self)._print()
[Previous line repeated 994 more times]
File ".code.tio", line 14, in _child_print_proto
print('DynamicChild saying: ')
RecursionError: maximum recursion depth exceeded while calling a Python object
super() is used when you're designing for inheritance. super(self.__class__, self) is used only when you're sabotaging inheritance. The only safe ways to do this involve some sort of static linkage to the class (the one that the method will be attached to, not the run time type of whatever instance it is called with), and my two solutions above are the two reasonable ways to do this (there's only one other option really, which is making the closure, and still passing the class and self explicitly, e.g. instead of defining __class__, just use super(cls, self) to explicitly use a closure variable; not sufficiently distinct, and kinda the worst of both worlds).
So, I said "there's three ways", but in fact there is a slightly nicer (but also less portable, because the API for types.FunctionType has changed over time, even though closures defined normally haven't) solution, which lets you make a utility function to bind arbitrary functions to arbitrary classes, instead of requiring you to wrap each such function in a closure-maker. And that's to literally rebuild the function as a closure directly:
import types
# Define function normally, with super(). The function can't actually be used as is though
def _child_print_proto(self):
print('DynamicChild saying: ')
super()._print()
# Utility function binding arbitrary function to arbitrary class
def bind_to_class(cls, func):
# This translates as:
# Make a new function using the same code object as the passed function,
# but tell it it has one closure scoped variable named __class__, and
# provide the class as the value to associate with it.
# This requires Python 3.8 or later (code objects didn't have a replace method
# until then, so doing this would be even uglier than it already is)
return types.FunctionType(func.__code__.replace(co_freevars=('__class__',)), func.__globals__, closure=(types.CellType(cls),))
DynamicParent = type('DynamicParent', tuple([]), {"_print": _parent_print_proto})
DynamicChild = type('DynamicChild', (DynamicParent,), {})
# Rebind the function to a new function that believes it was defined in DynamicChild
DynamicChild._print = bind_to_class(DynamicChild, _child_print_proto)
Like I said, it's super-ugly and would need rewrites pre-3.8, but it does have the mild advantage of allowing you to use bind_to_class with arbitrary super() using functions to bind them to arbitrary classes. I still think manually calling two-arg super is the safe/obvious way to go, but now you've got all the options.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
