Home > Software engineering >  How to pass function with super() when creating class dynamically?
How to pass function with super() when creating class dynamically?

Time:04-15

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'

CodePudding user response:

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()

Try it online!

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

CodePudding user response:

As mentioned by Mark super arguments can't be empty

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(self.__class__.mro()[-3], self)._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()
StaticChild saying: 
I'm StaticParent
DynamicChild saying: 
I'm DynamicParent
  • Related