Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Import subclass from a base class in Python

I have a base class that has a method that creates an instance of a subclass that is called the same as the input string.

This worked before by putting the subclasses and the base class in the same file, and doing something like globals()[name].

Now, however, I've split up the subclasses into other files. They each have an import base statement at the top, so I can't just simply import the subclasses in my base class or there'll be a chain of circular importing.

Is there any workaround for this?

In base.py:

from basefactory import BaseFactory
class Base:
    def __init__(self, arg1, arg2):
        ...
    def resolve(self, element):
        className = typing.getClassName(element)
        return BaseFactory.getInstance(className, element, self)

In basefactory.py:

from file1 import *
from file2 import *
...
class BaseFactory:
    @staticmethod
    def getInstance(name, arg1, arg2):
       subclass = globals()[name]
       return subclass(arg1, arg2)

In file1.py:

from base import Base

class subclass1(Base):
    def foo(self):
        return self.arg1
like image 316
Kevin Li Avatar asked Nov 30 '11 09:11

Kevin Li


2 Answers

You can move the import statement that is failing to the method that creates the subclass object.

like image 159
jcollado Avatar answered Oct 15 '22 03:10

jcollado


From what I'm understanding, you have:

  1. A base class
  2. A series of derived classes from the base class
  3. A factory method in the base class that instantiates the correct type of derived class
  4. The derived classes have been split into files, and they depend on the base class, but the factory method in the base class depends on the derived classes

One solution would be to create a separate function / class for the factory method, and put it in a separate file from the base class. This file could import all the files for the base class and derived classes without the circular reference.

For example:

# base.py:
class baseClass():
   def __init__(self):
      self.name = "Base"

# sub1.py:
from base import baseClass
class sub1Class(baseClass):
   def __init__(self):
      self.name = "sub1"

# sub2.py:
from base import baseClass
class sub2Class(baseClass):
   def __init__(self):
      self.name = "sub2"

# factory.py:
from sub1 import sub1Class
from sub2 import sub2Class # should not create an error
mapping = {'sub1': sub1Class, 'sub2': sub2Class}

def create(baseType):
  return mapping[baseType]

Actually, a better method might be to use type:

type(name, bases, dict) returns a new type object. This is essentially a dynamic form of the class statement. The name string is the class name and becomes the __name__ attribute; the bases tuple itemizes the base classes and becomes the __bases__ attribute; and the dict dictionary is the namespace containing definitions for class body and becomes the __dict__ attribute. For example, the following two statements create identical type objects:

>>> class X(object):
...     a = 1
...
>>> X = type('X', (object,), dict(a=1))

Why not move resolve into a manager class? Have a look at the domain class in Class factory in Python. I'm not sure if resolve is needed... you can get the class name directly from self.__class__.__name__, and use python functions like type() and isinstance() to check if they're particular types.

Also check out:
Can you use a string to instantiate a class in python?
Does python have an equivalent to Java Class.forName()?

like image 20
Adam Morris Avatar answered Oct 15 '22 03:10

Adam Morris