12

I'm trying to dynamically create a class using type() and assign an __init__ constructor which calls super().__init__(...); however, when super() gets called I receive the following error:

TypeError: super(type, obj): obj must be an instance or subtype of type

Here is my code:

class Item():    
    def __init__(self, name, description, cost, **kwargs):
        self.name           = name
        self.description    = description
        self.cost           = cost
        self.kwargs         = kwargs

class ItemBase(Item):
    def __init__(self, name, description, cost):
        super().__init__(name, description, cost)

def __constructor__(self, n, d, c):
    super().__init__(name=n, description=d, cost=c)

item = type('Item1', (ItemBase,), {'__init__':__constructor__})
item_instance = item('MyName', 'MyDescription', 'MyCost')

Why is super() inside the __constructor__ method not understanding the object parameter; and how do I fix it?

sadmicrowave
  • 39,964
  • 34
  • 108
  • 180

2 Answers2

9

Solution 1: Using cls = type('ClassName', ...)

Note the solution of sadmicrowave creates an infinite loop if the dynamically-created class gets inherited as self.__class__ will correspond to the child class.

An alternative way which do not have this issue is to assigns __init__ after creating the class, such as the class can be linked explicitly through closure. Example:

# Base class
class A():
  def __init__(self):
    print('A')

# Dynamically created class
B = type('B', (A,), {})

def __init__(self):
  print('B')
  super(B, self).__init__()

B.__init__ = __init__

# Child class
class C(B):
  def __init__(self):
    print('C')
    super().__init__()


C()  # print C, B, A

Solution 2: Using MyClass.__name__ = 'ClassName'

An alternative way to dynamically create class is to define a class inside the function, then reassign the __name__ and __qualname__ attributes:

class A:
  
  def __init__(self):
    print(A.__name__)


def make_class(name, base):

  class Child(base):
    def __init__(self):
      print(Child.__name__)
      super().__init__()

  Child.__name__ = name
  Child.__qualname__ = name
  return Child


B = make_class('B', A)


class C(B):
  
  def __init__(self):
    print(C.__name__)
    super().__init__()

C()  # Display C B A
Conchylicultor
  • 4,631
  • 2
  • 37
  • 40
  • Great follow up - I’m changing the accepted answer as I’m always open to better solutions :) – sadmicrowave Jun 29 '21 at 03:33
  • This article provides a good explanation of how `super(B, self)` works, under 'A super() Deep Dive': https://realpython.com/python-super/ – matthewpark319 May 17 '22 at 19:45
  • Is there any way to put B into a class or initiate it from a class call? – misantroop Nov 12 '22 at 13:56
  • @misantroop I'm not sure what you mean. An alternative way it to declare inside your `make_class('MyClass')` function: ``` class B(A): def __init__(self): super().__init__() ``` Then set `B.__name__ = 'MyClass'` and `B.__qualname__ = 'MyClass'`, and `return B` – Conchylicultor Nov 14 '22 at 13:31
6

Here is how I solved the issue. I reference the type() method to dynamically instantiate a class with variable references as such:

def __constructor__(self, n, d, c, h):
    # initialize super of class type
    super(self.__class__, self).__init__(name=n, description=d, cost=c, hp=h)

# create the object class dynamically, utilizing __constructor__ for __init__ method
item = type(item_name, (eval("{}.{}".format(name,row[1].value)),), {'__init__':__constructor__})
# add new object to the global _objects object to be used throughout the world
self._objects[ item_name ] = item(row[0].value, row[2].value, row[3].value, row[4].value)

There may be a better way to accomplish this, but I needed a fix and this is what I came up with... use it if you can.

sadmicrowave
  • 39,964
  • 34
  • 108
  • 180