2

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.

  • If I understood you right, you could look at meta-classes in python http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python, to make the class modify itself. Although, it is not good to use this unless you're absolutely sure of what you are doing and you just can't do without this. – adarsh Mar 27 '15 at 13:45

3 Answers3

4

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)
Dunes
  • 37,291
  • 7
  • 81
  • 97
3

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.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • Correct me if I am wrong but the factory function is not a class method of class Car but a "normal" function in the cars module? – Johan Bertenstam Mar 27 '15 at 14:13
  • 2
    Either or. It doesn't matter. More important is that you keep the same convention if you use this type of function in multiple modules/classes. Though, this in this specific instance a function has been created at module level. – Dunes Mar 27 '15 at 14:22
  • 1
    @Johan Right. But there really is little difference. If you'd name the function `Car` instead of `new_car` you'd have to look a lot deeper to find a difference. Which goes to say that there's hardly a point in insisting on doing this in an object constructor. – deceze Mar 27 '15 at 14:26
0

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

Community
  • 1
  • 1
justengel
  • 6,132
  • 4
  • 26
  • 42