Creating new instances of subclasses.

Nick Craig-Wood nick at craig-wood.com
Thu Jan 8 04:31:16 EST 2009


J. Cliff Dyer <jcd at unc.edu> wrote:
>  I want to be able to create an object of a certain subclass, depending
>  on the argument given to the class constructor.
> 
>  I have three fields, and one might need to be a StringField, one an
>  IntegerField, and the last a ListField.  But I'd like my class to
>  delegate to the proper subclass automatically, so I can just do:
> 
> >>> f1 = Field('abc')
> >>> f2 = Field('123')
> >>> f3 = Field('D,E,F')
> >>> f1.data
>  'abc'
> >>> f2.data
>  123
> >>> f3.data
>  ['D','E','F']
> >>> type(f1)
> <class '__main__.StringField'>
> >>> type(f2)
> <class '__main__.StringField'>
> >>> type(f3)
> <class '__main__.ListField'>
> 
>  I've come up with a solution, but I suspect there's something cleaner I
>  can do with the inheritance structure of __new__.  I don't like
>  explicitly leapfrogging over Field.__new__ to object.__new__.
> 
>  My attempt is below:
> 
>  def is_list(arg):
>      if ',' in arg:  return True
>      else:  return False
> 
>  def is_integer(arg):
>      try:  int(arg)
>      except ValueError:  return False
>      else:  return True
> 
>  class Field(object):
>      def __new__(cls, a):
>          if is_list(a):
>              return ListField(a)
>          elif is_integer(a):
>              return IntegerField(a)
>          else:
>              return StringField(a)
>      
>      def __init__(self, input):
>          super(Field, self).__init__(input)
>          self.data = input
> 
>  class IntegerField(Field):
>      def __new__(cls, a):
>          return object.__new__(cls, a)
>      def __init__(self, s):
>          super(IntegerField, self).__init__(s)
>          self.s = int(self.s)
>      
>  class ListField(Field):
>      def __new__(cls, a):
>          return object.__new__(cls, a)
>      def __init__(self, s):
>          super(ListField, self).__init__(s)
>          self.s = s.split(',')
> 
>  class StringField(Field):
>      def __new__(cls, a):
>          return object.__new__(cls, a)
> 
>  Is there a cleaner way to do this?  The main problem is that
>  Field.__new__ gets in the way of properly constructing the subclasses
>  once I've used it to select the proper subclass in the first place.

How about this minor modification?

# rest as above

class Field(object):
    def __new__(cls, a):
        if cls != Field:
            return object.__new__(cls, a)
        if is_list(a):
            return ListField(a)
        elif is_integer(a):
            return IntegerField(a)
        else:
            return StringField(a)

    def __init__(self, input):
        super(Field, self).__init__(input)
        self.data = input

class IntegerField(Field):
    def __init__(self, s):
        super(IntegerField, self).__init__(s)
        self.s = int(s)

class ListField(Field):
    def __init__(self, s):
        super(ListField, self).__init__(s)
        self.s = s.split(',')

class StringField(Field):
    pass

Or you could go for the full metaclass self registration scheme like
this, which is actually less code since I delegated the "is this ok"
test to the subclass - failing it returns a ValueError.

class Field(object):
    registry = []
    class __metaclass__(type):
        def __init__(cls, name, bases, dict):
            cls.registry.append(cls)
    def __new__(cls, a):
        if cls != Field:
            return object.__new__(cls, a)
        for subcls in cls.registry:
            if subcls == Field:
                continue
            try:
                return subcls(a)
            except ValueError:
                pass
        raise ValueError("Couldn't find subclass")
    def __init__(self, input):
        super(Field, self).__init__(input)
        self.data = input

# Raise a ValueError in init if not suitable args for this subtype

class IntegerField(Field):
    def __init__(self, s):
        s = int(s)
        super(IntegerField, self).__init__(s)
        self.s = s

class ListField(Field):
    def __init__(self, s):
        if ',' not in s:
            raise ValueError("Not a list")
        super(ListField, self).__init__(s)
        self.s = s.split(',')

class StringField(Field):
    pass



-- 
Nick Craig-Wood <nick at craig-wood.com> -- http://www.craig-wood.com/nick



More information about the Python-list mailing list