Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

in Python 3, can a superclass polymorphically call a subclass's constructor

I want to create method (say copy) in a class (Parent) that will return an object of either the class or the subclass that invokes it. I want type(x) == type(x.copy()).

None of the approaches I tried were satisfactory.

  • Using the superclass constructor returns the superclass (make senses but I figured it was worth a try).
  • Creating a function init_me in each subclass that the super class uses but that defeats the purpose of inheritance.
  • I started to explore __new__ and __init__, but quickly decided Python must have a better way.

Sample code

class Parent(object):
    def __init__(self, p1=p1_default, p2=p2_default, p3=p3_default):
        ...   # common stuff
        self._special_suff()
    def copy_works_if_subclass_does_extra(self):
        return self.init_me()
    def copy_only_does_superclass(self):
        return Parent()
    def copy_with_init(self):
        return self.__init__()
    def whoami(self):
        print('I am just a parent')

class Dad(Parent):
    def _special_stuff():
        ...     # Dad special stuff
        return 
    def whoami(self):
        print('I am a dad')
    def init_me(self):
        return Dad()

class Mom(Parent):
    def _special_stuff():
        ...     # Mom special stuff
        return 
    def whoami(self):
        print('I am a mom')
like image 250
Mike Ulm Avatar asked Jul 20 '26 21:07

Mike Ulm


2 Answers

If I understand correctly, you're trying to write a copy method in your base class that will still work when called on an instance of a derived class. This can be made to work, but it's only easy if your child classes only expect the same set of arguments as the base class. If their __init__ method expects different arguments you'll need separate copy methods for each derived class.

Here's a quick example of how it can work. The trick is to call type(self) to get the right class, and then call the class with appropriate constructor arguments to get the new instance:

class Base(object):
    def __init__(self, arg1, arg2, arg3):
        self.attr1 = arg1
        self.attr2 = arg2
        self.attr3 = arg3

    def copy(self):
        cls = type(self)
        return cls(self.attr1, self.attr2, self.attr3)

class Derived(Base):
    def __init__(self, arg1, arg2, arg3):
        super().__init__(arg1, arg2, arg3)
        self.some_other_attr = "foo"

In practice this tends not to work as well, since the Derived class will usually want to take an extra argument to set up its extra attribute. An option that might work in that situation is to use the copy module rather than writing your own copy method. The function copy.copy will be able to copy many Python instances without any special support.

like image 170
Blckknght Avatar answered Jul 22 '26 12:07

Blckknght


You are overcomplicating things a lot. Minimal example with a simple constructor implemented on the child class:

import copy

class Parent():
    def whoami(self):
        print('Just a parent')
    def __init__(self, name):
        self.name = name
    def copy(self):
        # Maybe copy.deepcopy instead
        return copy.copy(self)


class Dad(Parent):
    def whoami(self):
        print('I am a dad')
    def __init__(self, name):
        super().__init__(name)
        self.gender = 'Male'

You don't even need a constructor in Python if you don't need. Or you can have one on the superclass and nothing on the child.

Some usage:

>>> dad = Dad("Clark Griswold")
>>> dad.name
'Clark Griswold'
>>> dad.whoami()
I am a dad
>>> isinstance(dad, Dad)
True
>>> isinstance(dad, Parent)
True
>>> type(dad.copy()) == type(dad)
True
like image 27
Harald Nordgren Avatar answered Jul 22 '26 13:07

Harald Nordgren