HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Model cars as classes

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
modelcarsclasses

Problem

I am learning about object oriented programming in Python using classes. I have attached an image of what I had in mind when structuring my classes. This is supposed script model cars. I want to know if the way that I set up my code it the ideal way of modeling cars.

A few things:

-
There are no syntax errors. I can create instances without errors.

-
The subclasses uses *args to get the attributes of the parent class. Is this the correct way of doing that?

-
I am using Python 2.7.5

-
I have included a few example instances of the class

-
Each class only has an __init__ method and a vehicle_print method. I have not yet added any other methods.

```
class Vehicle(object): # no instance of this class should be created

def __init__(self, typ, make, model, color, year, miles):
self.typ = typ
self.make = make
self.model = model
self.color = color.lower()
self.year = year
self.miles = miles

def vehicle_print(self):
print('Vehicle Type: ' + str(self.typ))
print('Make: ' + str(self.make))
print('Model: ' + str(self.model))
print('Color: ' + str(self.color))
print('Year: ' + str(self.year))
print('Miles driven: ' + str(self.miles))

class GasVehicle(Vehicle):

def __init__(self, fuel_tank, *args):
self.fuel_tank = fuel_tank
Vehicle.__init__(self, *args)

def vehicle_print(self):
Vehicle.vehicle_print(self)
print('Fuel capacity (gallons): ' + str(self.fuel_tank))

class ElectricVehicle(Vehicle):

def __init__(self, energy_storage, *args):
self.energy_storage = energy_storage
Vehicle.__init__(self, *args)

def vehicle_print(self):
Vehicle.vehicle_print(self)
print('Energy Storage (Kwh): ' + str(self.energy_storage))

class HeavyVehicle(GasVehicle): # no instance of this class should be created

def __init__(self, max_weight, wheels, length, *args):

Solution

A couple of things to consider

1) As a style thing, I'd prefer **kwargs to *args in your constructors. This makes things clearer since there's no chance that a change to a base class initializer will invalidate other code in subclasses. Something like @MichaelUrman's example:

class Vehicle(object):
    """Represent a vehicle."""
    def __init__(self, **kwargs):
        self.info = kwargs


Allows you to flexibly specify values while providing class level defaults that vary on application. Super (as mentioned in other answers) is great for avoiding repetition here. For example

class FourByFour (Vehicle):
    def __init__(self, **kwargs):
        # provide a default transmissions
        if not 'transmission' in kwargs: kwarg['transmission'] = 'Generic 4x4'
        super (FourByFour, self).__init__(**kwargs)


In the comments to @MichaelUrman's example, OP asks how to create a specific class of car. A common python idiom (which is less common in other languages) is to create 'thin' subclasses that vary primarily by default content and then occasionally override default methods. This is common in cases where you want a lot of classes which are mostly the same but differ in details and you don't expect to manage lots of subtle hierarchical changes. From the 10,000 ft level this is the same using helper classes, since you're just populating a set of variables in a structured way; I prefer it to using helper functions because it allows you to add custom methods as necessary while keeping things data driven.

For example:

class Vehicle(object):   
"""Represent a vehicle."""
    DEFAULTS = {'wheels':4, 'doors':2, 'fuel':'gasoline', 'passengers':2}

    def __init__(self, **kwargs):
       kwargs.update(self.DEFAULTS)
       self.info = kwargs 

    def drive(self):
       print "Vroom"

class Sedan(Vehicle):
    DEFAULTS = {'wheels':4, 'doors':4, 'fuel':'gasoline', 'passengers':5}

def Hybrid(Sedan):
    DEFAULTS = {'wheels':4, 'doors':4, 'fuel':'smug', 'passengers':4}

    def drive(self):
        print "purrrrrr..."

class Van(Vehicle):
    DEFAULTS = {'wheels':4, 'doors':3, 'fuel':'gasoline', 'passengers':2, 'cargo':[]}

    def unload(self):
       if not self.cargo:
          print "no cargo loaded"
       else:
          print "unloading"
       return self.cargo


Conceptually this is halfway between 'traditional' inheritance heavy OOP and @MichaelUrman's data customization approach. All three have their applications, and the neat thing about python is that it's very good at both @MichaelUrman's method and mine -- which is not true for languages obsessed with type-checking and signature maintenance.

2) On the strategy level, in the long term you'll benefit from a finer grained approach. Engines, transmissions, and tires for example might be their own classes instead of aspects of vehicle (a station wagon and a sedan, for example, might share everything but body styles). The class hierarchy becomes less of an issue as you do more mix-and-match customization by creating collections of components. The usual pretentious programmer way to say this is 'prefer composition over inheritance'

Code Snippets

class Vehicle(object):
    """Represent a vehicle."""
    def __init__(self, **kwargs):
        self.info = kwargs
class FourByFour (Vehicle):
    def __init__(self, **kwargs):
        # provide a default transmissions
        if not 'transmission' in kwargs: kwarg['transmission'] = 'Generic 4x4'
        super (FourByFour, self).__init__(**kwargs)
class Vehicle(object):   
"""Represent a vehicle."""
    DEFAULTS = {'wheels':4, 'doors':2, 'fuel':'gasoline', 'passengers':2}

    def __init__(self, **kwargs):
       kwargs.update(self.DEFAULTS)
       self.info = kwargs 

    def drive(self):
       print "Vroom"


class Sedan(Vehicle):
    DEFAULTS = {'wheels':4, 'doors':4, 'fuel':'gasoline', 'passengers':5}

def Hybrid(Sedan):
    DEFAULTS = {'wheels':4, 'doors':4, 'fuel':'smug', 'passengers':4}

    def drive(self):
        print "purrrrrr..."

class Van(Vehicle):
    DEFAULTS = {'wheels':4, 'doors':3, 'fuel':'gasoline', 'passengers':2, 'cargo':[]}

    def unload(self):
       if not self.cargo:
          print "no cargo loaded"
       else:
          print "unloading"
       return self.cargo

Context

StackExchange Code Review Q#40190, answer score: 8

Revisions (0)

No revisions yet.