Johan Bertenstam
Johan Bertenstam

Reputation: 21

Have baseclass creation return child object in Python

I have a base class with two subclasses, say Car with subclasses HeavyCar and LightCar. Is it possible to have the creation of a base class return an object of a subclass dependent of a variable? Like this:

weightA = 2000
myAcar = Car(weigthA) # myAcar is now of type HeavyCar
weightB = 500
myBcar = Car(weightB) # myBcar is now of type LightCar

I know that the normal way to do this would be to look at the weight variable and then decide what type of object I want and then create that specific object. However I would like to leave it up to my class to decide which type it should be and not have to bother about that outside the class, i.e not have to look at the variable weight at all.

Upvotes: 2

Views: 950

Answers (3)

deceze
deceze

Reputation: 522075

Even if it's somehow possible to do this, it's not really a very sane object design. Things can be too dynamic at some point. The sane solution would simply be a factory function:

class LightCar(Car):
    maxWeight = 500
    def __init__(self, weight):
        assert(weight <= self.maxWeight)
        self._weight = weight

# analogous for HeavyCar

def new_car(weight):
    if weight <= LightCar.maxWeight:
        return LightCar(weight)
    ..

From the point of view of the consumer, it makes little difference:

import cars

car = cars.new_car(450)
print(type(car))

Whether you write cars.new_car(450) or cars.Car(450) hardly makes any difference, except that the former is a dedicated factory function which does exactly what you're wanting: it returns a LightCar or HeavyCar depending on the weight.

Upvotes: 3

Dunes
Dunes

Reputation: 40693

You can override __new__ to make it return the desired type. However, it would just be simpler to define a function that does the same, as it would be less prone to errors.

using __new__

class Car(object):    
    def __init__(self, weight):
        self.weight = weight

    def __new__(cls, weight):
        if cls is Car:
            if weight > 1000:
                return object.__new__(HeavyCar)
            else:
                return object.__new__(LightCar)
        else:
            return object.__new__(cls)

class LightCar(Car):
    def __init__(self, weight):
        super(LightCar, self).__init__(weight)
        self.speed = "fast"
class HeavyCar(Car):
    pass

assert isinstance(Car(10), LightCar)
assert Car(10).weight == 10 # Car.__init__ invoked
assert hasattr(Car(10), "speed") # LightCar.__init__ invoked as well

assert isinstance(Car(1001), HeavyCar)
assert Car(1001).weight == 1001 # Car.__init__ invoked

Using a function

def create_car(weight):
    if weight > 1000:
        return HeavyCar(weight)
    else:
        return LightCar(weight)

assert isinstance(create_car(10), LightCar)

Upvotes: 4

justengel
justengel

Reputation: 6320

It may be possible by overwriting the __new__ method, but that would be complex and take a long time.

... Just use a function or have a child class that uses multiple inheritance to contain light car and heavy car. If you make another child class you will have to deal with method resolution conflicts.

def car(weight):
    if weight > 2:
        return HeavyCar(weight)
    return LightCar(weight)

__new__

http://rafekettler.com/magicmethods.html

https://www.python.org/download/releases/2.2/descrintro/#new

mro

http://www.phyast.pitt.edu/~micheles/python/meta2.html

Method Resolution Order (MRO) in new style Python classes

Upvotes: 0

Related Questions