J. Cliff Dyer <j...@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 <n...@craig-wood.com> -- http://www.craig-wood.com/nick -- http://mail.python.org/mailman/listinfo/python-list