I'm using python and I want to be able to create an object of a base class and have it initialize the object using the __init__ of a subclass. I use a function defined outside of the classes to determine which subclass' __init__ method should be called. I found a way to make it work but its behaving in a way I don't understand.

To illustrate the strange behavior, I've written the following code. I create a base class (called parent). It has two subclasses (child1 and child2). In the __init__ of the base class, I call the function "choose" which returns an object of one of the derived classes (depending on the value of var). The strange thing is I can append to the array from the base class and it actually changes the array but when I try to change the value of a variable like "str", the "str" of base class does not change. I didn't really think this whole strange approach would work anyway but it confuses me because it half-works.

class parent:
    array = [1]
    str = "parent"
    def __init__(self, var):
        choose(var)
        
class child1(parent):
    def __init__(self, var):
        self.array.append(var)
        self.str = "child1"
        
class child2(parent):
    def __init__(self, var):
        self.array.append(var)
        self.str = "child2"

def choose(var):
    if var == 1:
        child1_obj = child1(var)
        print child1_obj.array
        print child1_obj.str
        return child1_obj
    elif var == 2:
        child2_obj = child2(var)
        print child2_obj.array
        print child2_obj.str
        return child2_obj
    else:
        return None
    

#testing code
person = parent(2)
print person.array
print person.str

The output I receive is:

[1, 2]
child2
[1, 2]
parent

The strange thing is not python's behavior, but your idea of initializing a base class object using the __init__ method of a subclass. The purpose of __init__ methods is to build an object, and therefore, the __init__ method of class child2 should be used to create a child2 instance. Usually in python, the constructor (__init__ method) of a subclass invokes the constructor of the parent class like this

class parent:
  array = [1]
  str = "parent"
  def __init__(self, var):
    self.array.append(var)

class child1(parent):
  def __init__(self, var):
    parent.__init__(self, var)
    self.str = "child1"

class child2(parent):
  def __init__(self, var):
    parent.__init__(self, var)
    self.str = "child2"

Following this pattern, if you want to create a person initialized with child1's constructor, the best way is to create a child1 instance. For example, you could write a function

def new_person(var):
  if var == 1:
    return child1(var)
  elif var == 2:
    return child2(var)
  else:
    return parent(var)

person = new_person(2)

Now the person returned in the previous call is a child2 instance, and as such it's also a parent instance, which means that you can use it in every function which expects a parent argument. Trying to convert it to a true 'parent' object is a strange and unnecessary thing (in fact python allows it, you could write person.__class__ = parent , but don't do that, it's bad programming style).

Edited 6 Years Ago by Gribouillis: n/a

This looks totally crazy spaghetti for me, but I tried to decipher what you are trying to do.

class parent:
    array = [1] ## class variable
    str = "person" 
    def __init__(self, var):
        choose(var) ## choose(2), value never used, only side effect to array

        
class child1(parent):
    def __init__(self, var):
        self.array.append(var)
        self.str = "child1"
        
class child2(parent):
    def __init__(self, var):
        self.array.append(var)
        self.str = "child2"
        
def choose(var):
    if var == 1:
        child1_obj = child1(var) ## new instance with value var==2
        ## init puts in array 2 in addition to inherited [1]
        print child1_obj.array   ## [1,2] as inherited parent array
        print child1_obj.str  ## child1
        return child1_obj
    elif var == 2:
        print 'parent.array', parent.array ## print [1]
        child2_obj = child2(var) ## new chidl2, put 2 to inherited parent class array [1,2] 
        print child2_obj.array  ## print [1,2]
        print 'parent.array', parent.array ## print [1,2]
        print child2_obj.str ## print child2
        return child2_obj
    else:
        return None
    

#testing code
person = parent(2)
print person.array ## should return the class value [1]
print person.str   ## "parent", persons classes str

Picking str for a variable name is not good, since it is a Python function. So let's change that to str1. In function choose() you return the child object, but never do anything with it. You have to let class parent know which child you picked, so change your code slightly ...

class parent:
    array = [1]
    str1 = "parent"
    def __init__(self, var):
        child = choose(var)
        self.str1 = child.str1
        
class child1(parent):
    def __init__(self, var):
        self.array.append(var)
        self.str1 = "child1"
        
class child2(parent):
    def __init__(self, var):
        self.array.append(var)
        self.str1 = "child2"

def choose(var):
    if var == 1:
        child1_obj = child1(var)
        print child1_obj.array
        print child1_obj.str1
        return child1_obj
    elif var == 2:
        child2_obj = child2(var)
        print child2_obj.array
        print child2_obj.str1
        return child2_obj
    else:
        return None
    

#testing code
person = parent(2)
print person.array
print person.str1

Also, by convention class names should be capitalized to aid in code readability.

I realize that you are new to OOP and classes, so I would recommend to heed Dr.
Gribouillis' basic design advice.

Edited 6 Years Ago by vegaseat: do

The reason that I've used inheritance in this bad way is because I'm trying to solve the following problem:

We have multiple python scripts which use my script to parse a certain type of file. In the past, we've only had one filetype and so we had one class. However, its my task to add the ability to parse other filetypes (with other formatting). I'm attempting to do this by adding a subclass corresponding to each new filetype. Each subclass' constructor would parse a particular file in a certain way and initialize the data (contained in lists and arrays) which would be defined in the base class. However! the catch is that all those other scripts create objects of the base class, NOT of the subclasses. Rather than change every one of those scripts to create objects of the subclass which applies to their filetype, I want to allow them to just create an object of the base class, pass in the filename, and (using the file's extension) have the base class initialize the correct subclass.

I actually have this working right now. If you notice in my code posted above, the array of the baseclass was properly changed (which is surprising seeing as this isn't proper programming... I guess). HOWEVER, the string variable of the base class was NOT changed... I have no idea why it works for arrays and list but not for variables such as int, string, etc. Seeing as my code works, I'll probably keep it, I'm just wondering why it doesn't work with all my variables.

I would love ideas on the proper way to accomplish what I'm trying to accomplish with inheritance. I know I could just have a large series of if...else statements in the base class constructor which would parse each file the right way and I could get rid of all the subclasses, but it seems there should be a way to have a separate subclass for each filetype.

Thanx for your help.

In general, the OO way of getting what you want is a Factory pattern: Something external to the class instance (it can be a static class method, a method in a WhateverFactory class, or a free function) that accepts the appropriate input parameters and returns an instance of the appropriate child class. You use this instance as if it were a base class instance.
In python, everything is first class, so you could create something like this dispatch = {filetype:constructor,...} and use it to make objects like this obj = dispatch[filetype](params)

If you don't want derived objects at all, a proper way would be to write a class hierearchy to initialize your objects. For example

class Parser:
  def __init__(self, var):
    initialize = choose_initializer(var)
    initialize(self, var)

class Initializer:
  def __init__(self, parser, var):
    pass

class TypeAInitializer(Initializer):
  def __init__(self, parser, var):
    Initializer.__init__(self, parser, var)
    # do whatever you want here

class TypeBInitializer(Initializer):
  def __init__(self, parser, var):
    Initializer.__init__(self, parser, var)
    # do whatever you want here

The sole purpose of Initializers would be to initialize Parser instances depending on the file type.

Edited 6 Years Ago by Gribouillis: n/a

How about dict of extensions and their init version. You already ifs added to original. Maybe new file types need not cause any change to objects interface. Or do you want to new scripts to do more with new type support than unaware old scripts?

I thought something like this:

class parent1:
    array = [1]
    str11 = "parent"
        
class child1(parent1):
    def __init__(self, var):
        self.array.append(var)
        self.str1 = "child1"
        
class child2(parent1):
    def __init__(self, var):
        self.array.append(var)
        self.str1 = "child2"

choose={1:child1,2:child2}

def parent(var):
    return choose[var](var)
  
#testing code
person = parent(2)
print person.array ## [1,2]
print person.str1   ## "child2"

Edited 6 Years Ago by pyTony: str1

Thank you griswolf and tonyjv for the Factory pattern solution. I will make "parent" be a function which returns an object of the correct subclass instead of "parent" being the name of the base class. Thus, when the unaware scripts call:

person = parent(2)

They will still get back an object of type base class but they won't know that they called a function rather than creating an object of the base class directly. Also, they can still use person.whatevermemberfunction as needed.

Thank you all for your help.

This question has already been answered. Start a new discussion instead.