3

I'm trying to duplicate and then modify a class programmatically but I'm running into problems with python 3's magic super for example the following

class Base():
    def __init__(self):
        print("Base init")

class Orginal(Base):
    def __init__(self):
        super().__init__()
        print("Orginal init")

Modified = type(Orginal.__name__, Orginal.__bases__, dict(Orginal.__dict__))
Modified.f = lambda self: print("f")

m = Modified()

raises

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

So I'm wondering, is there someway I can help super() find the right __class__ cell in a class created through type()?

2 Answers2

2

Note: I corrected the spelling of Original in this answer


It appears that the problem is that when your Modified object is being instantiated and super is called, its being called like: super(Original, self) since those are the default arguments for super. Since Original is not a superclass of Modified (check isinstance(m, Original)), python is not allowing you to call super in that way.

The following code does something similar to yours but better illustrates the problem.

class Base():
    def __init__(self):
        print("Base init")

class Original(Base):
    def __init__(self):
        super().__init__()
        print("Orginal init")

class Modified(Base):
    def __init__(self):
        super(Original, self).__init__()  # illegal 

Modified.__name__ == Original.__name__

m = Modified()  # raises same Exception that your your code does

Adding Original to the bases of Modified will make it work:

Modified = type(Original.__name__, (Original,) + Original.__bases__, dict(Original.__dict__))

Edit:

Per the comments, the above suggestion can be simplified since all the methods contained within Original will be included in Modified without needing to pass the dict in the type call:

Modified = type(Original.__name__, (Original,), {})

and going one step further, if you don't want Modified to be a subclass of Original, you could simply make Modified a copy of Original and then add attributes the same as you do in your example:

from copy import deepcopy
Modified = deepcopy(Original)
Modified.f = lambda self: print("f")

m = Modified()

Looking at this answer, it appears that you cannot use deepcopy to copy classes, and given that you have that super() call in your Original.__init__ method, Modified be a subclass of Original (unless you also modify the __init__ method in your Modified class after it's been "copied").

Community
  • 1
  • 1
Billy
  • 5,179
  • 2
  • 27
  • 53
  • 1
    FYI, If `Original` is in bases, no need to add `Original.__bases__` and `dict(Original.__dict__)`: `Modified = type(Original.__name__, (Original,), {})` should be enough. – falsetru Nov 26 '16 at 14:37
  • Using deepcopy is good idea, it is exactly what I needed- except it doesn't seem to actually work. If I overwrite any of Original's attributes Modified's also are changed! – Daniel Bunting Nov 29 '16 at 14:17
2

According to the documentation,

Also note that, aside from the zero argument form, super() is not limited to use inside methods. The two argument form specifies the arguments exactly and makes the appropriate references. The zero argument form only works inside a class definition, as the compiler fills in the necessary details to correctly retrieve the class being defined, as well as accessing the current instance for ordinary methods.

So, the super() in the code is equivalent to super(Orginal, self).__init__(), and it's determined in compile time.

falsetru
  • 357,413
  • 63
  • 732
  • 636
  • It actually adds a `__class__` cell containing the class, wich you could manipulate from within that function. But Daniel Bunting already knows that, judging from the last part of his question. He is asking how to set the cell's value. – CodenameLambda Nov 26 '16 at 14:56
  • Thanks for the hint: that's the missing piece of a puzzle I've been banging my head on for the last couple of days. I don't know why I've missed it. – Timus Jun 14 '23 at 17:22