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)
4
  • 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")? Commented Aug 23, 2013 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. Commented Aug 23, 2013 at 12:37
  • 1
    The internal representation should not define the API; so if OP wants subclasses, there might be a reason. Commented Aug 23, 2013 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. Commented Aug 23, 2013 at 14:53

3 Answers 3

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)
Sign up to request clarification or add additional context in comments.

2 Comments

Nicer. And I like using def instead of lambda for defining the __init__().
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.
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'

1 Comment

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.
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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.