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