How to instatiate a class of which the name is only known at runtime?

Jack Diederich jack at performancedrivers.com
Wed Sep 10 15:15:27 EDT 2003


On Wed, Sep 10, 2003 at 02:15:38AM -0600, kosh wrote:
> On Wednesday 10 September 2003 01:12 am, Max M wrote:
> > Max M wrote:
> > > import class1, class2, class3
> > >
> > > def Factory(class_type):
> > >     if class_type='class1':
> > >         return class1()
> > >     elif class_type='class2':
> > >         return class2()
> > >     elif class_type='class2':
> > >         return class3()
> > >
> 
> use
> 
> def Factory(class_type):
>   factory = {
>     'class1':class1,
>     'class2':class2,
>     'class3':class3}
>   try:
>     return factory[class_type]()
>   except KeyError:
>     pass 
> 

metaclasses are your friend, here is one I use frequently for factoires.

class Register(type):
  """A tiny metaclass to help classes register themselves automagically"""

  def __init__(cls, name, bases, dict):
    if ('register' in dict): # initial dummy class
      setattr(cls, 'register', staticmethod(dict['register']))
    elif (getattr(cls, 'DO_NOT_REGISTER', 0)):
      # we don't want to register this non-concrete class
      delattr(cls, 'DO_NOT_REGISTER')
    elif (object not in bases):
      cls.register(name, cls)
    return

class Widget(object):
  __metaclass__ = Register
  all = {}
  def register(name, cls):
    Widget.all[name] = cls

  def factory(name):
    return Widget.all[name]
  factory = staticmethod(factory) # just use this class like a namespace

class C1(Widget): pass
class C2(Widget): pass
class C3(Widget): pass

# instantiate a C3 by name
c3_instance = Widget.factory('C3')()

Every class that inherits Widget and doesn't have a DO_NOT_REGISTER attribute 
will put it's name:class into the Widget.all dictionary.

I typically use a short hierarchy to break up the code a bit

class WidgetFacory(object):
  """this class only has static factory methods"""
  __metaclass__ = Register
  all = {}
  # register() and factory() as above

class Widget(WidgetFactory): # we inherit 'WidgetFactory' to get the metaclass
  """This class is the base functionality of Widget classes,
     This is a virtual base class for Widgets that can't be instatiated
     by the WidgetFactory interface"""
  DO_NOT_REGISTER = 1 # tell the factory to forget we exist

  def __init__(self, *args, **opts):
    # your code here

# now define the classes that can be instantiated from the WidgetFactory
class Mallet(Widget): pass
class Hoop(Widget): pass
class Ball(Widget): pass


hmm, the post is about to get longer.
If you repeat the above pattern enough, write a func to do all the above
for you, so your code might look like

class Widget(object): pass # your real definition of the base Widget class

# setup the factory, and rebind the name Widget to a class that inherits it
(WidgetFactory, Widget) = make_factory(Widget)

I use a slightly different technique in my unit tests, cut-n-pasted here
it defines the class-tracker using Register and returns the base object.
It also defines a main() method for the test suite.  My test-coverage
class is in here too, but that is extra.  It just defines a TestCase
that checks for test coverage versus implemented tests.

Last things first, here is how it is used
""" t_libUtil.py  -  test 'libUtil' module """
import test # defined below
import libUtil

(Test, main) = test.setup(libUtil)

class SimpleSum(Test):
  we_test = [libUtil.simple_sum]
  def test_sum(self):
    f = libUtil.simple_sum
    x = [0,0]
    self.assertEqual([0,3], f(x,[1,3]) or x)

if (__name__ == '__main__'):
  main()


# module 'test.py'
import unittest

class Register(type):
  """A tiny metaclass that keeps track of all classess"""
  def __init__(cls, name, bases, dict):
    if ('register' in dict): # initial dummy class
      setattr(cls, 'register', staticmethod(dict['register']))
    elif (object not in bases):
      cls.register(name, cls)
    return

def setup(module):
  class Test(unittest.TestCase, object): # TestCase is an old-style class
    __metaclass__ = Register
    alltests = [] # all Test classes
    allfuncs = [] # list of all the functions they test

    def register(name, cls):
      Test.alltests.append(cls)
      Test.allfuncs += cls.we_test

  class TestCoverage(Test):
    """make sure we have tests for all the funcs and classes in the module"""
    we_test = []
    def test_coverage(self):
      # find all functions and classes in the module
      need = []
      for (name, val) in module.__dict__.items():
        if (callable(val)):
          need.append(val)
        try:
          if (val.__class__.__name__ == name): # this finds classes defined in the module
            need.append(val)
        except: pass

      for (ob) in need:
        good = ob in Test.allfuncs
        if (not good):
          raise self.failureException("No Test coverage: %s" % (str(ob)))

  def main():
    suite = unittest.TestSuite()     
    test_classes = Test.alltests
    for cls in test_classes:
      suite.addTest(unittest.makeSuite(cls))
    result = unittest.TestResult()
    suite(result)
    msg = []
    if (result.errors):
      msg.append('Failure')
    if (result.errors):
      msg.append('Error')
    print "%s: %d %s" % (module.__name__, result.testsRun, " & ".join(msg) or 'Success')
    for (err) in result.errors:
      print "\n".join(map(str, err))
    for (failure) in result.failures:
      print "\n".join(map(str, failure))
    return
  
  return (Test, main)


In places like unittest where we already do heavy introspection
using metaclasses to do the introspecition is usually a big win.

long-posted-ly,
    
-jackdied





More information about the Python-list mailing list