4

I have a base class from which I would like to make many subclasses. The subclasses differ only in the arguments used to call the base class during instantiation. The example below shows how to make a subclass Apple. Is there a way to do this programatically, without having to code the subclass' __init__ method? This seems like a job for metaclasses, but in this case I cannot modify the base class.

apple = {'color': 'red', 'shape': 'sphere'}
pear = {'color': 'yellow', 'shape': 'cone'}
melon = {'color': 'green', 'shape': 'prolate'}

class Fruit(object):
    def __init__(self, color, shape):
        self.color = color
        self.shape = shape        

class Apple(Fruit):
    def __init__(self):
        Fruit.__init__(self, **apple)
MarmiK
  • 5,639
  • 6
  • 40
  • 49
user807435
  • 103
  • 5
  • 1
    If they only differ by initial values, what's the point of them being subtypes? Why not just `def apple(): return Fruit("red", "sphere")`? – nmclean Aug 23 '13 at 12:37
  • 2
    What is the motivation for wanting to use subclasses? This appears like an obvious use case for instances of `Fruit`, not subclasses. – Joel Cornett Aug 23 '13 at 12:37
  • 1
    The internal representation should not define the API; so if OP wants subclasses, there might be a reason. – Alfe Aug 23 '13 at 12:42
  • There are reasons, but I admit they're pretty trivial: the code where the subclasses are used is much clearer if the classes bear the names of their object, and I was simply curious if this was possible. – user807435 Aug 23 '13 at 14:53

3 Answers3

6

See the type() function.

def make_fruit(name, kwargs):
    def my_init(self):
        Fruit.__init__(self, **kwargs)
    return type(name, (Fruit,), {'__init__': my_init})

Apple = make_fruit('Apple', apple)
user9876
  • 10,954
  • 6
  • 44
  • 66
  • Nicer. And I like using `def` instead of `lambda` for defining the `__init__()`. – Alfe Aug 23 '13 at 12:53
  • I prefer this over @falsetru's response since it doesn't use a ``lambda``. Using the ``dict`` argument in ``type`` to define ``__init__`` is the key. Thanks. – user807435 Aug 23 '13 at 14:57
3

Using type:

class Fruit(object):
    def __init__(self, color, shape):
        self.color = color
        self.shape = shape        

apple = {'color': 'red', 'shape': 'sphere'}
pear = {'color': 'yellow', 'shape': 'cone'}
melon = {'color': 'green', 'shape': 'prolate'}

g = globals()
for clsname, attrs in [('Apple', apple), ('Pear', pear), ('Melon', melon)]:
    def temp(attrs):
        g[clsname] = type(clsname, (Fruit,), {
            '__init__': lambda self: Fruit.__init__(self, **attrs)
        })
    temp(attrs)

>>> a = Apple()
>>> p = Pear()
>>> m = Melon()
>>> assert a.color == 'red' and a.shape == 'sphere'
>>> assert p.color == 'yellow' and p.shape == 'cone'
>>> assert m.color == 'green' and m.shape == 'prolate'
falsetru
  • 357,413
  • 63
  • 732
  • 636
  • Good solution (I can abort my writing then …). But I have to disapprove of modifying the result of `globals()`. This is not working in all cases. (`locals()` isn't any better.) There are lot of Qs in SO about this. The only really working way of changing the current scope programmatically is via `exec` and that's an ugly hack. – Alfe Aug 23 '13 at 12:52
0

I think the solution you're seeking does not exists: I all you subclasses share the same constructor, then the only thing which makes the class unique is its name. And I don't think you want to have a generic constructor which check the class name to choose what to do.

So I think:

  • either you re-define the constructor in each subclass, and precise which parameter to pass to the parent constructor,
  • either you put the specific constant values in some class member, and the constructor use it to call the parent constructor

See interesting thing in this other thread: Class factory in Python

Community
  • 1
  • 1
Alexis
  • 707
  • 8
  • 33