Creating new instances of subclasses.

Bruno Desthuilliers bdesth.quelquechose at free.quelquepart.fr
Wed Jan 7 15:41:55 EST 2009


J. Cliff Dyer a écrit :
> 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.

Not only that, but the base class should know nothing of its subclasses. 
As Paul suggested, a better solution would be to make Field a factory 
function (and rename the Field class to BaseField).

Also and FWIW, since your Field subclasses are responsible for doing the 
conversion, you could as well use this to find out the concrete class to 
use for a given value, ie (not tested):

class Field(object):
     __classes = []
     __default = None

     @classmethod
     def register(cls, subclass, is_default=False):
         if is_default:
             if cls.__default is not None:
                 raise WhateverError("only one default subclass, thanks")
             cls.__default = subclass

         cls.__classes.append(subclass)

     def __new__(cls, value):
         for subclass in cls.__classes:
             try:
                 return subclass(value)
             except (TypeError, ValueError):
                 continue

         if cls.__default is not None:
             return cls.__default(value)

         raise ValueError("no appropriate subclass for '%s'" % value)


class BaseField(object):
     # common code here

class FloatField(BaseField):
     def __init__(self, value):
         self.value = float(value)

Field.register(FloatField)

class ListField(BaseField):
     def __init__(self, value):
         if "," in value:
             self.value = value.split(",")
         raise ValueError("could not convert '%s' to a list" % value)

Field.register(ListField)

class IntegerField(BaseField):
     def __init__(self, value):
         self.value = int(value)

Field.register(IntegerField)

class StringField(self, value):
     def __init__(self, value):
         self.value = str(value)

Field.register(StringField, is_default=True)

Now this may just be overkill for your needs - I don't have enough 
context to know !-)





More information about the Python-list mailing list