[Tutor] Beginner Question

Steven D'Aprano steve at pearwood.info
Wed Oct 23 05:27:21 CEST 2013


On Tue, Oct 22, 2013 at 04:25:59PM +0200, Sven Hennig wrote:
>  Hello, I would like to learn a programming language and have decided to use
> Python. I have some programming experience and doing well in Python. What
> really causes me problems is OOP.
> I'm just dont get it... I'm missing a really Practical example. In every
> book I've read are the examples of such Class Dog and the function is bark. Has
> anyone an OOP example for me as it is really used in real code, so I can
> better understand the concept? I do not know why this is so hard for me.

I can sympathise. You wouldn't believe how long it took me to really 
grok object-oriented programming. I just didn't get it, until I started 
thinking of OOP as being just syntax for keeping functions (called 
"methods") close to the data they belong with. There is more to OOP than 
that, but that was my first step: OOP helps you keep your functions 
close to the data they work with.

In conventional procedural languages, if you had a function that 
operates on a string, you would write something like this:

Struct string = ... # define a string here

# later in the file, perhaps many pages later:

function upper(some_string):
    result = make a new string
    for char in some_string:
        if char = 'a': add 'A' to result
        if char = 'b': add 'B' to result
        ...
        if char = 'z': add 'Z' to result
        else add char to result
    return result

(Aside: don't program uppercase like that! There are more efficient 
ways, and in Python, it is already provided!)

So the problem with this is that your data (say, strings) and the 
functions that operate on your data (say, upper, lower, and many others) 
can end up being defined far away from each other, which makes editing 
the code painful.

With OOP, the first change is that the syntax is changed to bring the 
functions (called "methods") together with their data:

class string:
    # put code here to define the "string" data class
    # now define all the functions that work on strings
    def upper(self):
        # code for "upper" goes here

    def lower(self):
        # code for "lower" goes here

    # any other string methods go here


Then, later, instead of writing:

mystring = "blah blah blah"
print upper(mystring)  # this doesn't work since upper is a method

we have a slightly different syntax:

print mystring.upper()  # use this instead


That's all you need to know to start using object oriented programming 
in Python! Many operations are implemented as methods, for instance 
strings have upper and lower methods, lists have append and remove 
methods, and many more. You'll soon learn the operations like len() that 
aren't methods, but old-school functions.

As I said, there is a lot more to OOP than just making it easier to 
organise your program files, but that was the realisation that helped me 
get OOP.


The next step is to understand "inheritance". Inheritance means that you 
can create a new class (the subclass) which inherits -- borrows -- data 
and methods from another class (the super class). Here is a stupid 
example:

class StupidStr(str):  # inherit from str
    def upper(self):
        return "***"


This class StupidStr is exactly like the ordinary string, except the 
upper() method has been replaced to do something trivial and stupid:

py> s = StupidStr("Hello World")
py> s.lower()  # inherits the behaviour of normal str
'hello world'
py> s.upper()  # overrides the normal upper method for my stupid one
'***'


Why would you do this? Well, obviously you wouldn't do something so 
stupid except as an exercise. It's hard to give *simple* real life 
examples that aren't contrived, but here's one that works for me:

Suppose you're writing software to control a model car. You might start 
off by collecting some basic defaults:

class DefaultModelCar:
    model = "Unknown"

    BRAKE_COMMAND = "B"
    FORWARD_COMMAND = "F"
    REVERSE_COMMAND = "R"
    ACCELERATE_COMMAND = "A"
    TURN_LEFT_COMMAND = "TL"
    TURN_RIGHT_COMMAND = "TR"

    def send_command(self, command):
        # Send a command to the model car.
        # Put the code here to actually communicate with the car.
        ...

    def forward(self):
        # Drive forward.
        if self.direction == "Reverse":
            # Brake first, then move forward.
            self.stop()
        self.send_command(self.FORWARD_COMMAND)
        self.send_command(self.ACCELERATE_COMMAND)
        self.direction = "Forward"

    def reverse(self):
        if self.direction == "Forward":
            self.stop()
        self.send_command(self.REVERSE_COMMAND)
        self.send_command(self.ACCELERATE_COMMAND)
        self.direction = "Reverse"

    def stop(self):
        self.send_command(self.BRAKE_COMMAND)
        self.direction = "Stopped"



and so on. Now, this default set of commands probably won't work for any 
actual model car, but at least you can run it and test that it works as 
you expect:


car = DefaultModelCar()  # Create a car instance
car.forward()
if car.direction == "Forward":
    print("Okay, car is moving forward")
else:
    print("There's a problem...")
car.stop()



Now you want to control a couple of specific models:

class SpeedyCar(DefaultModelCar):
    model = "Speedy Car Model X"

    BRAKE_COMMAND = "10"
    FORWARD_COMMAND = "11"
    REVERSE_COMMAND = "12"
    ACCELERATE_COMMAND = "34"
    TURN_LEFT_COMMAND = "41"
    TURN_RIGHT_COMMAND = "42"


class MegaCar13(DefaultModelCar):
    model = "Mega Car Model M13"

    BRAKE_COMMAND = "%9"
    FORWARD_COMMAND = "%1"
    REVERSE_COMMAND = "%2"
    ACCELERATE_COMMAND = "%6"
    TURN_LEFT_COMMAND = "%7"
    TURN_RIGHT_COMMAND = "%8"


And that's it! If you've done your programming right, most of the hard 
work has been done in the default class, and all you need to do is 
change a few settings, and it should just work. 

(In reality, each model car will probably have it's own special way of 
receiving commands. But let's just pretend that all model cars accept 
commands the same way, only the commands themselves are different.)


Sometimes you'll find that you need to override a method with one a 
little different:


class MegaCar14(MegaCar13):
    model = "Mega Car Model M14"

    # Brakes are crap on the M14.
    def stop(self):
        for i in range(5):
            self.send_command(self.BRAKE_COMMAND)
        self.direction = "Stopped"

 
But everything else works just as before. Now you can have a race:


a = SpeedyCar()
b = MegaCar13
c = MegaCar14

cars = [a, b, c]


# Start the cars, let them drive for 10 seconds, and see which gets 
# further.
for car in cars:
    car.forward()

time.sleep(10)

for car in cars:
    car.stop()



Notice that even though each model car needs different commands to 
control it, all those details are hidden away inside the class. From the 
outside, you use exactly the same commands to control it: forward, stop, 
reverse, etc.



-- 
Steven


More information about the Tutor mailing list