Saturday, November 2, 2013

PyTutorial: More on classes

This is the 8th in a series of posts.  If you want to go the the start, go here.

In the previous lesson, you got your first introduction to classes.  As I mentioned then, classes are a way to describe relationships between information and actions.  Exactly how to determine the best way to relate various pieces of information and actions together is more of an art than science (although there is some science to it).  I'll go into some of the science (and science-fiction) of writing classes in a future tutorial on design patterns,  but at this point, your most important friend when trying to figure out which classes to create is elegance:
Write your classes in such a way as to make your coding tasks simpler, easier to understand, and less difficult to test.
The more programming experience you have under the belt, the easier it will become for you to get a sense for a clean, simple design.

In this lesson, I want to expand on the ideas of the previous tutorial and show you some more interesting things that can be done with classes.  Hold on tight! ;)

A Family Tree

One powerful thing that you can do when creating classes is to "steal" the code of one class and expand on it to create another one.  For example, say you had an "Car" class that deals with miles per gallon, number of passengers, safety ratings and whatever else you may have it do.  And then, you want to create another class, called "Taxi", that can do everything that a Car can do, but more!  For instance, as Taxi may have a license number, a rate per mile, and a phone number that it can be reached at.  Of course, a Taxi still has all the properties that a Car has: miles per gallon, number of passengers, etc.  

How would you code these classes?

One thing you can do is "copy and paste" all the code you have for Car, rename it "Taxi", and then add all the properties for Taxi.  This is evil.  :)  In general, if you find yourself copy-pasting too much code, you're probably doing something wrong.  The reason is that when you copy-paste code, you end up with similar or identical code spreading about your program, and if you discover a bug in one place, you have to look for all the other places that you may have to fix.   This is hell on earth.

Another option is to add all the properties that you want for Taxi straight into the Car class.  With this approach, you have one class that contains all the properties for both Car and Taxi.  This is another evil.  The problem with this approach is that you end up polluting the very idea behind a class: namely, that it should contain information and actions that relate to each other!  Now, when you want a Car (and not a Taxi), you end up with all the code for Taxi in there anyways!  This is stinky, and brings you a step closer to what is known in the profession as "spaghetti code":  code that is so confusing and messy that it looks like a pile of spaghetti.  Another hell on earth. :)

What is a nice programmer to do? :)

Related Classes

Lucky for us, there is an elegant way to solve the above problem.  We can keep the Car class clean and simple, and then create a new class, Taxi, that magically gets all the information and actions from the Car class, while adding its own information and actions that are specific to taxis.  In tech-speak, this is classed inheritance, and conceptually, it looks something like this:

Isn't this beautiful?  I thought you'd like! ;)

With this approach, if you create an object of type Taxi, it will automatically have access to all the information and actions that an object of type Car would have, without having to nastify your code.

Now, let's take a look at how inheritance is achieved in Python:

Can you figure out what it does?  Type it in and see what happens! :)

The key things to notice are:
  1. The Car class is completely "normal".  There is nothing new here.
  2. When creating the Taxi class, the Car class is mentioned in parenthesis.  This tells Python that you want the Taxi class to inherit all the properties (information and actions) from the Car class.  In tech-speak, the Taxi class is derived from its base class, Car.
  3. The Taxi class has access to all the methods and data-members in the Car class.  Note that Pick_Up_Client() is accessing self.num_passengers and self.Go_Somewhere() as if they're directly a part of the Taxi class.
  4. The Taxi class's constructor (__init__ method) needs to explicitly call the constructor of its parent (base) class.  If it didn't do it, the Taxi objects wouldn't be created properly (they wouldn't get all the data-members from the Car class.)  Try commenting out the explicit call to Car.__init__() and see what happens.
  5. Objects of type Taxi can access the methods and data members from the Car class as if they're directly defined in the Taxi class.  Notice the call to taxi1.Fill_Up_Gas().
Pretty neat, huh?  Can you think of other situations in which inheritance can make your code more elegant?

How about a FoodItem class that contains nutritional information about anything edible.  You can then have another class, ManufacturedFoodItem that inherits from FoodItem and has information about who are its manufacturers, as well as what are its key ingredients.
Assignment 8.1: draw up a class diagram for both the FoodItem class as well as the ManufacturedFoodItem class.
Assignment 8.2: write up the code that implements the above two classes.
A nice thing about inheritance is that more than one class can inherit from any given base class.  For instance, you can also have a Produce class that inherits from FoodItem and contains information about foods that actually grow.  This information can include geographical location that it grows best in, and months of years that it bears its fruits.
Assignment 8.3: draw up the class diagram for the Produce class as described above.
Assignment 8.4: Implement the code for the Produce class.
Sometimes, deciding between creating a sub-class (thus using inheritance), or just adding more information to an existing class can be confusing.  For instance, say you had a Balloon class,  and you wanted to add a color attribute, should you add it to the Balloon class, or create more classes, such as GreenBalloon, RedBalloon, etc?

Well, I have two "rules of thumb".  The first goes back to the taxi example above, and is basically that:
If you're considering adding an attribute of a function that is only relevant to a few of the objects of that class, you may want to create a sub-class for it.
In the Car / Taxi example, adding a Taxi License attribute is only relevant for taxis, and thus shouldn't be added into the car class.  On the other hand, every balloon has a color, thus the color attribute should just be added to the balloon class.

But even before this rule of thumb, you were probably getting a strange sense of a stomach ache when I was bringing up the option for a GreenBalloon, RedBalloon classes, etc.  Something inside you was cringing, telling you that this is just plain wrong.  Which brings me to the other rule of thumb for you:
If you're about to do something that makes you want to vomit, most likely, you're doing it wrong.  Take a step back and think of how you can do things differently.
BTW, the above rule applies for many aspects of life, not just class inheritance.  You're welcome!

Endless Fun

You may be wondering if you can inherit from a class that already inherits.  Well, you don't have to wonder much longer!  The answer is a definite yes! :)  How exciting?!!

Say, for the sake of example, that taxicabs on our beloved moon are significantly different from our early kind.  Lunar cabs require a purple elephant assigned one for each cab!  In that case, you may want to inherit from the Taxi class to create a LunarTaxi class, perhaps do something like this:


Assignment 8.5: Remember our Produce class?  Good.  Now, I'm not a great farmer, not by any standard.  But just follow me here and let's assume that there are three different kinds of produce: land produce, water produce, and air produce.  And they're all radically different from each other.  Well, at least different enough to warrant different classes.  For instance, we can assume that earth produce require certain amount of earth to grow.  They may also require a certain type of earth.  On the other hand, wa

Can you square the rectangle?

Here's a riddle for ya: say you have a Shape class.  From it, derive three classes: CircleRectangle, and Square.  Something like this:

As a reminder, because all three are inherited from Shape, all three have the color attribute, as well as the draw_on_screen method.  And the riddle is: what's wrong with this picture?  Do you sense a problem?  Is anything wrong?

Well, don't pull all  your hair out.  Save some for the next riddle. ;)  The problem lies in the fact that a square is a type of rectangle.  On the face of it, it doesn't seem like a big deal, in fact, there may be some advantages to this: the Square class gets to have only a "side length" attribute, while the rectangle needs both "width" and "height".

But the main problem here is that a square is indeed a type of rectangle!  Meaning, you now have two distinct ways of describe the same thing!  To draw a square, you can either use a Square class, or use a Rectangle class in which the width equals the height.  

Now, if you wanted to compare various shapes, to see if any are the same, you would have to complicate your code.  Instead of just comparing circles with circles, squares with squares, and rectangles rectangles, you would also have to compare squares to rectangles, to see if by any chance, your rectangle is really a square in disguise!  Perhaps not the end of the world, but a lesson to be learned:
Try not to have different ways of representing the same information in your code.  But, if for some hellish reason you must, then at the very least keep this problem in mind.
Now that I got you all nervous, pop quiz! (a.k.a assignment 8.6)

I'm going to give you two types of information, and my question for you is how should I structure the classes?  You have four possible answers that you can give me:

  1. Place both pieces of information into one class (like our green and red balloon example)
  2. Use two completely separate classes (like how our balloon and car classes have nothing to do with each other)
  3. Use two classes, but have one derive from another (like our car and taxi example)
  4. Use two classes, having both of them derive from a third, more abstract class.  (like our food item, produce, and manufactured food item classes)
You'll also have to elaborate on your answer. Got it? Good. BTW, there may be more than one good answer, you just need to have a good explanation.  Don't you just hate that? Let's go:
  1. A male soccer player and a female soccer player.
  2. A bird and a frog.
  3. A car and a piece of toast.
  4. A pager (what's that, you ask?) and a phone.
  5. A land-line phone (what's that, you ask?) and a cellular phone.
  6. A car and a Volvo.
  7. A car and a motorcycle.
  8. A pancake and maple syrup.
  9. A strawberry and a grape.
  10. An airplane and a pair of shoes.
  11. An employee and an employer.
  12. A nurse and a doctor.
  13. A purse and a wallet.
  14. A curse and a blessing.
Whew!  I hope that this was as hard for you as it was for me! :)

Multiple Inheritance

There is a hell on our programming earth known as multiple-inheritance.  Basically, it's a way for one class to inherit the attributes and methods of two or more unrelated classes.  For instance, you can have a class, called, Monster, that inherits from both the Car class and the Balloon class.

As expected, this can make for quite the mess, especially if some of these classes share a an attribute or method of the same name!

The good news, is that in Python, you don't really need to use multiple-inheritance. Therefore, I won't waste your time teaching it to you. :)  In languages like C++ and Java, multiple-inheritance makes more sense in some situations , and I may go into that in some future lesson.  For now, just know that it's possible, and that it's hell.  Avoid.

Interfaces / Polymorphism / Abstract Virtual Methods / Method Override!

Holy cow!  In a language like C++ or Java, all of the above can mean rather complicated things.  But in Python, they're basically short (or long) for having two classes with the same method name, that do the same thing, yet slightly differently.

For instance, if you go back to our Shape/Circle/Square/Rectangle class diagram above, there is one more little thing that makes no sense.  Which brings me back to my other riddle:  besides the square being a rectangle, what's wrong with that class diagram?

You may have noticed, that the Draw_on_screen method is only defined in Shape!  This means that it will be available for all the shapes that inherit from it, but it will do the same thing!  This doesn't really make sense on two levels:

  1. How can we draw an abstract shape?  What does it look like?  It looks like an error message to me. :)
  2. When drawing a circle, we want it to look different than a square.  Thus the Draw_on_screen method, although essentially doing the same thing for a circle as well as a square, will have to do it a little differently in each case.  In the circle's case, it will have to draw a circle, and in the square's case, it will have to draw a square.

Perhaps a better way to think of it is thus:

Thus the method, Draw_On_Screen is implemented in each class slightly differently.  In essence, this is a description of an interface, that is, every class that inherits from Shape needs to implement, at the very least, the method Draw_On_Screen in order to work as expected.

In Python, there's no explicit way to force a programmer to write a particular method in all the classes that inherit from a given class.  You just have to make it clear in the code that this is what's expected.  For instance, one can add a little comment in the main class that this needs to be done.  In other words, it would look something like this:

Type it in and see what happens!   Now, having the method, Draw_On_Screen() have the same name across different classes let's you treat the different classes, at least on some level, as being the same.  Basically, you can assume that every class that is derived from Shape() will have the method Draw_On_Screen().  Thus, you can have a list of different shapes, and then go through the list and draw them all, like this:

That is true beauty.  You can actually call the method Draw_On_Screen() on any instance of a class that is derived from Shape(), without even knowing which class it is!  It may draw a circle, or it may draw a rectangle.  You don't know, and frankly, you don't care!  You just want to draw all the shapes on the screen, it doesn't matter to you which shapes they are!

Wow! :)
Assignment 8.7: create a class, Triangle, and have it derive from the Shape() class.  Of course, have it implement the Draw_On_Screen() method.  Add a triangle to the my_shapes list above, and see what happens!
 Keep in mind that you should only be doing this when the different classes actually need to do things differently.  The shapes example is great, because one simply can't draw a shape without knowing which shape it is that is being drawn!  Yet, every shape can be drawn.

Generally, any time you need to manage a bunch of related classes (the all inherit from the same class), you probably will need to act on the objects in some sort of unified way.  When this happens, you'll find yourself wanting to implement similar methods across the different classes.
Assignment 8.8: think of a situation in which you need to implement identical methods across different classes.  Then go ahead and write the code for it, just to test it out! :)

If all fails, try and try again...

Now I'm going to take you on a slight tangent.  It still has to do with classes, but in a very different way.

Remember how back in Lesson 6 I showed you how you can use the built-in function, raw_input to get numbers from the user?  It went something like this:
>>> a = raw_input("Type in a number:")
>>> num_a = int(a)
>>> print num_a

Now this works nice as well, so long as the player is playing nice and actually types in a number... But what happens when the player types in something besides a number?  Say, she types in "howdy"?

Try it!

Did you get a big old error message in red that caused your entire program to quit?
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'howdy'

What this error message tells you is that you can't convert "howdy" into a number.  Duh?! :)

In tech-speak, these sort of errors are called exceptions, probably because they represent special, unexpected scenarios.  And the reason that I'm bringing this up here (in a lesson on classes), is that in Python, exceptions are implemented using classes.  But more on this in a bit.

The problem in our example, of course, is that you don't want someone who is actually using your program to see this!  You want to be able to handle such errors gracefully.  In this case, it probably means asking the user to type in a number again... :)

Well, you're in luck!  Python provides you with a simple, yet powerful way to handle such exceptions. Check this out:
Can you figure out what happens here?  Type it in and try it out! :)

Basically, we have a "try-except" clause, which is sort of like our "if-else" clause, but different.  In the "try-except" clause, Python tries to run the code that's after the "try" statement.  If all goes well, good for you!  But, if there's some sort of exception, the code after the "except" statement gets run!  The key here is that the code after the "try" runs as usual, while the code after the "except" only runs if the code after the "try" fails!  That is, the code after the "try" has raised an exception.

BTW, the "try-except" clause doesn't have to be inside a while loop.  It's just that in the above example, it seemed to have made sense to put it inside one.

Believe it or not, but there's a hidden problem with the above code.  Can you guess what it is?  No? :)  Ok, I'll just tell you what it is.  :)

The "except" clause, as we have it, actually catches any and every kind of exception.  Even python syntax errors!! (Syntax errors is tech-speak for writing code that doesn't even make sense to the computer.  Yea, it's that bad.)  For instance:
What on earth is alsdfjalsdfj??? I don't know, and Python sure as hell doesn't know either!  Yet, because of our little "except" clause, you don't get to see that Python is very confused.  In fact, you get the "I said *number*... :)" printed on the screen instead.  Even when the user typed in a number!!  If you get rid of the "if-except" clauses, you would be able to see that this exception is raised:
NameError: name 'alsdfjalsdfj' is not defined
Basically, what happened is that even though you wanted to catch that the user didn't type in a number as expected, what you actually caught was your (or my, actually) programming mistake!

To avoid this mess, the better approach would be to do something like this:

Now, we're explicitly telling Python to only catch ValueError exceptions!  But, what is this thing, called "ValueError", you ask?  Well, you may have guessed that it's a name of a class!  And if that's what you guessed, well, you guessed right!  :)  Woohoo!

Assignment 8.9: write program that asks the user to type in a salary amount in dollars (int), and a tax rate (float) and prints out how much money she will have left over after taxes.  [Hint: A salary of $500 and a tax rate of 30.7% should leave you with 500 * (1 - 30.7 / 100) = $346.5]

Python gives you even more tools when handling exceptions, can you figure out what the following code does?

Type it in and play around with it a bit... See if you can get a feel for it.

  • You can have more than one "except" clause, catching different exceptions.  In my example, I'm catching both ValueError and NameError exceptions.
  • except ... as... lets you actually get access to the object that is associated with the exception.  In our case, the class of exception caught is NameError, and the object with the actual error message is called e.  You can read more on this here.
  • raise is the way to generate exceptions.  In our case, we're just raising the same exception that we got!  Not very useful, since all we're doing is mocking the programmer.  Perhaps not useful, but definitely fun! :)  Again you can read on it more in here.
  • else can also be used with a try-except clause (that is, without an if!)  The code after the "else" only gets run if no exception was caught!
  • finally is used to run code at the very end, no matter if an exception was caught or not!  It's useful in order to perform "clean-up" tasks of sorts.  In our example, it was pretty much useless.
That's a lot of information all in one place!  I do recommend that you read more on exceptions here.  I just wanted to give you a taste for them.  In most cases, just using try and except is all you really need.

Out with the old, in with the new...

This is a bit random, but Python actually has two kinds of classes.  It's usually not very important, but I'm only telling you this just in case you bump into it one day.  The old classes is what I've been teaching you (sorry.)  The new classes are just like the old classes, but they derive from a class called "object" (or from another class that derives from object, etc. etc.), like this:

Fascinating.  I don't want to go into why this happened or why this is important. Most likely, it will never matter to you.  I just want you to be aware of this little issue.  But if you really care, you can read more about it here. Enjoy! :)

Minesweeper time!

Ok, so going back to your minesweeper game...  Is there anything from this lesson that you can incorporate into it?

The most obvious thing is the exception handling.  You definitely don't want the program to end just because the user had a typo.  So fix that.

But what about the whole inheritance business?  Mostly likely it will just add unnecessary complexity to your code.  Remember that keeping you code elegant is more important than making it fancy.  But, if in your mind using inheritance in Minesweeper can actually simplify your life, then go ahead and do it!  But don't feel that you must use inheritance in every programming project.  Heck, you don't even need to use classes, but it's more likely that you'll want to. :)

In the next lesson I'll talk about packages.  Packages are pretty easy to learn (at least in theory), and are a boat-load of fun!  They give you, the programmer, some real power to do some real things (like get stuff from the web.)  And in Python, it's all good and fun!  It's going to be an adventure, I promise! :)  Hold on tight...

No comments:

Post a Comment