Learning Python 3 with the Linkbot/Advanced Functions Example

From LinkbotLabs
Jump to: navigation, search

Functions and Variable Scope

In Python, as well as many other programming languages, variables are only valid in a certain "scope". For instance, if I declare a variable inside of a function, other functions will not be able to access the variable. For instance, consider the following code:

def myfunc():
    x = 5
    print(x)
 
def myotherfunc():
    print(x) # Error!

The code makes a new variable named "x" inside of myfunc(). Then, we declare another function called "myotherfunc()" that tries to print the value of x, but this will cause an error. The error is due to the fact that the function "myotherfunc" cannot access the variable "x" declared inside of "myfunc()".

This raises the question: Is it possible for two functions to share a variable? As with many other programming languages, the answer is "Yes". Variables which can be accessed from any location are known as global variables. In Python, there is a special keyword called global that is used to access global variables. Take a look at the following program:

# File: 01_global.py
 
x = 5
 
def printX():
    global x
    print(x)
 
def changeX1():
    global x
    x = 20
 
def changeX2():
    x = 30
 
printX() # prints "5"
x = 10
printX() # prints "10"
changeX1()
printX() # prints "20"
changeX2()
printX() # prints "20" again

In this program, there are several things going on. First, notice we create a variable named "x" that is outside of all functions. In programmer lingo, we say that this variable is declared in "global scope".

Next, we define a function called printX(). Here is our first encounter with the global keyword. The global keyword here tells Python to look for an existing variable named "x" in the global scope and make it available in the current scope, which is inside the function "printX()". We must keep in mind though that after declaring "global x", the variable "x" inside the printX() function refers to the original variable "x". Thus, any changes to the variable "x" inside of the printX() function will also effect the original "x" variable.

Then we define a function called "changeX1()". This function is similar to our first "printX()" function, except instead of just printing the value of "x", we change its value to "20".

In addition, we define one last function called "changeX2()". You will notice here that we do not use the global keyword to define x. Because we do not use the the global keyword here, the line x = 30 actually creates a new variable "x" inside the changeX2() function and assigns it a value of 30. Note that this new "x" variable inside the changeX2() function is an entirely separate variable from our original "x", even though they share the same name. As such, the line x = 30 does not change our global x; only the "local" x.

At the end of the program, we try calling our functions in a certain order. First, we call printX(). Since x was declared with an initial value of "5", this line of code simply prints "5".

Next, we execute the line "x=10". Because this statement occurs in the same scope as our declaration of the original x variable, the original x variable's value is changed to 10. When we call printX() again, the value "10" is printed out.

Then we call changeX1(). Remember that this function changes the value of the global variable "x" to 20. After calling changeX1(), we call printX() again. This time, the value "20" is printed out.

Finally, we call changeX2(). Remember that inside the changeX2() function, we do not declare the variable "x" to be global. At first glance, we may expect that the next printX() function would print "30", but it does not. This is because the changeX2() function actually creates its own "x" variable that is completely unrelated to our original "x" variable and assigns the value "30" to its own local "x" variable, but not the global one. That's why when we call printX() one last time, it prints "20" because the call to changeX2() never modified the global variable "x".

Callback Functions: Detecting Button Presses on the Linkbot

As you may have noticed now, when we write functions, we typically use the functions by calling them. Calling a function just means executing it by typing the function name followed by parenthesis (), sometimes with arguments inside of the parenthesis. A callback function is slightly different: A callback function is a function that a programmer (you) writes, but that you do not call directly. Instead, you tell the computer system to execute your function whenever certain events occur.

We will demonstrate this by writing a function that gets called every time a Linkbot's button is pressed in the following example code.

# File: 02_callbacks.py
 
import linkbot
 
def myCallback(button, event, timestamp):
    print("Buttons: ", button)
    print("Event: ", event)
    print("Timestamp:", timestamp)
    print()
 
robotID = input('Enter robot ID: ')
robot = linkbot.CLinkbot(robotID)
robot.enableButtonEvents(myCallback)
 
input('Try pressing some buttons on the robot. Press "Enter" to quit.')

Notice in this code that we define a function myCallback(), but we never actually call the function. In other words, you never see code that looks like myCallback(1, 2, 5), with parenthesis and arguments after the function name. Instead, the only other place we see myCallback is as an argument to the function enableButtonEvents(). The function enableButtonEvents() is a special Linkbot function that takes a callback function as its argument. After this line is executed, it basically informs the program to call whatever function you fed into enableButtonEvents() any time a button on the Linkbot is pressed or released.

When you run the program, try pressing some buttons on the Linkbot. You will notice that any time you press or release a button, the function myCallback() will be called and the button and event data will be printed out. Also note that the function that you pass into enableButtonEvents() must be of a specific format: It must be able to take 3 arguments where the first argument is the button number, the second is an event type (number), and the third is a timestamp in milliseconds. If you supply enableButtonEvents() with a callback function that does not take the right kind of arguments, errors can result.

Other Linkbot Callback Functions and Events

Detecting button presses is only one of a few different types of events that can be generated by a Linkbot. The other types of events your Linkbot can emit are accelerometer events (emitted when the robot detects a change in its accelerometer readings), and encoder events (emitted when the robot senses that its wheels have moved). The following sample program prints a message any time accelerometer events or encoder events are detected.

# File: 03_event_callbacks.py
 
import linkbot
 
def accelCallback(x, y, z, timestamp):
    print("Accel Event!", x, y, z, timestamp)
 
def encoderCallback(joint, angle, timestamp):
    print("Encoder Event!", joint, angle, timestamp)
 
robotID = input('Enter robot ID: ')
robot = linkbot.CLinkbot(robotID)
robot.enableAccelerometerEvents(accelCallback)
# enableEncoderEvents() expects 2 arguments: A granularity (in degrees) and
# a callback function. The granularity specifies the minimum amount a 
# Linkbot's joint needs to move before the encoder callback is called.
# Setting it to "5" means that the callback will only be called if the joint
# moves 5 degrees or more.
robot.enableEncoderEvents(5, encoderCallback)
 
input("Try moving the robot around and moving the robot's joints." 
      "Press 'Enter' to quit.")

A Linkbot Reaction Timer

This program turns the Linkbot "B" button into a reaction timer. When you run the program, the Linkbot will remain quiet for a random amount of time. Suddenly, the Linkbot will turn green and begin beeping. Your job is to press the "B" button as fast as you can after the robot starts beeping. The program will figure out the amount of time it took you to react to the beeping, and that amount of time is your reaction time.

# File: 04_reaction_time.py
 
import time
import linkbot
import random
import sys
 
robotID = input('Enter robot ID: ')
robot = linkbot.CLinkbot(robotID) #enter the linkbot ID
time_start=0
button_pressed=False
game_start=False
 
def callback(buttons, event, timestamp):                                  # 01
    if (buttons & 0x02) and event:                                        # 02
        global game_start                                                 # 03
        global time_start
        global button_pressed
        button_pressed=True                                               # 04
        if game_start==False:                                             # 05
            print("You pressed the button to soon")
        else:
            print("your  reaction time is :", time.time()-time_start)
        global robot
        robot.setBuzzerFrequency(0)
 
robot.enableButtonEvents(callback)                                        # 06
input("Press enter to begin")                                             # 07
robot.setLedColor(255, 0, 0)                                              # 08
time.sleep(random.random()*7+3)                                           # 09
if button_pressed:                                                        # 10
    sys.exit()
time_start=time.time()                                                    # 11
game_start=True                                                           # 12
robot.setBuzzerFrequency(440)                                             # 13
robot.setLedColor(0, 255, 0)                                              # 14
while True:                                                               # 15
    time.sleep(1)
    if button_pressed:
        sys.exit()

Program Synopsis

This program turns a Linkbot into a reaction test "game" to test the reaction time of human beings. When the program is started, it connects to a Linkbot and prepares it to run a reaction test. When the game starts, it initially turns the LED color to red. The person playing the game must pay close attention to the Linkbot, because it will turn green and begin to beep at any time. As soon as the Linkbot turns green and beeps, the player must press button "B" as fast as possible. The program records the time between turning green and detecting the button press, which is the "reaction time" of the player, and displays that time to the player.

Additionally, if the player presses the button before the robot beeps (The player is trying to cheat), the program will display a message telling the player that they pressed the button too soon.

Program Detailed Description

Each item corresponds to a tag in the source code that looks like # 01

  1. First, we define a "callback function". A callback function is basically an ordinary function, except that it isn't meant to be called by the programmer directly. Instead, callback functions are usually registered to trigger based on some event. When the event occurs, the system automatically calls the callback function. Here, we write a callback function that should be called when the user presses a button on the Linkbot.
  2. This line detects to see if button B is pressed, and that event is true. If event is true, than the button is in the process of being pressed down. If it is false, the event is indicating that the button is being released.
  3. The next three lines declare three variables. The global keyword indicates that these variables should be grabbed from the "global" scope, declared earlier in the program. Thus, if any of these variables changes inside the callback function, the global instance of the variable is also changed.
  4. If the program ever gets to this point, we know that the player has pressed the button, so we set the global variable button_pressed to true.
  5. Here, we test to see if the game has actually started. If the user clicked on the Linkbot's button before the game was started, game_start will be false.
  6. Now, we have declared our callback function, but we haven't called it or done anything with it yet. This line registers our callback function to the Linkbot's button handler with the enableButtonEvents function. This function tells the Linkbot "Whenever there is a button event, I want you to execute the function that I give you". In our case, we give it our callback() function, so that our callback() function is executed whenever a Linkbot's button is pressed or released.
  7. The input() function is used here to pause the program until the player is ready. When the player presses "enter", the program continues to the next line.
  8. Set the LED color to red
  9. Pause the program for a random amount of time. random.random() generates a random number between 0 and 1. We take that number and multiply it by 7, turning it into a random number between 0 and 7, and add 3 to it. The end result is a random number between 3 and 10. This line of code pauses the program for 3 to 10 seconds before continuing.
  10. Here, we are done sleeping and we check to see if the player has already pressed the button. If they did, they pressed it too soon, so we exit.
  11. Now here, we have officially started the game. We need to take a note of exactly when the game started by storing the current time.
  12. Since the game has now started, we set the global variable game_start to "true".
  13. Start beeping the robot
  14. Turn the robot's LED green.
  15. Finally, we wait for the user to press the button. If the player has a really slow reaction time, this might take a while, so we go into an infinite loop while we wait for the player.

Recursion

Some people find this section useful, and some find it confusing. If you find it confusing you can skip it. Now we will do a walk through for the following program:

def mult(a, b):
    if b == 0:
        return 0
    rest = mult(a, b - 1)
    value = a + rest
    return value
print("3 * 2 = ", mult(3, 2))

Basically this program creates a positive integer multiplication function (that is far slower than the built in multiplication function) and then demonstrates this function with a use of the function. This program demonstrates the use of recursion, that is a form of iteration (repetition) in which there is a function that repeatedly calls itself until an exit condition is satisfied. It uses repeated additions to give the same result as mutiplication: e.g. 3 + 3 (addition) gives the same result as 3 * 2 (multiplication).

Question: What is the first thing the program does?
Answer: The first thing done is the function mult is defined with the lines:
def mult(a, b):
    if b == 0:
        return 0
    rest = mult(a, b - 1)
    value = a + rest
    return value
This creates a function that takes two parameters and returns a value when it is done. Later this function can be run.
What happens next?
The next line after the function, print("3 * 2 = ", mult(3, 2)) is run.
And what does this do?
It prints 3 * 2 = and the return value of mult(3, 2)
And what does mult(3, 2) return?
We need to do a walkthrough of the mult function to find out.
What happens next?
The variable a gets the value 3 assigned to it and the variable b gets the value 2 assigned to it.
And then?
The line if b == 0: is run. Since b has the value 2 this is false so the line return 0 is skipped.
And what then?
The line rest = mult(a, b - 1) is run. This line sets the local variable rest to the value of mult(a, b - 1). The value of a is 3 and the value of b is 2 so the function call is mult(3,1)
So what is the value of mult(3, 1) ?
We will need to run the function mult with the parameters 3 and 1.
So what happens next?
The local variables in the new run of the function are set so that a has the value 3 and b has the value 1. Since these are local values these do not affect the previous values of a and b.
And then?
Since b has the value 1 the if statement is false, so the next line becomes rest = mult(a, b - 1).
What does this line do?
This line will assign the value of mult(3, 0) to rest.
So what is that value?
We will have to run the function one more time to find that out. This time a has the value 3 and b has the value 0.
So what happens next?
The first line in the function to run is if b == 0:. b has the value 0 so the next line to run is return 0
And what does the line return 0 do?
This line returns the value 0 out of the function.
So?
So now we know that mult(3, 0) has the value 0. Now we know what the line rest = mult(a, b - 1) did since we have run the function mult with the parameters 3 and 0. We have finished running mult(3, 0) and are now back to running mult(3, 1). The variable rest gets assigned the value 0.
What line is run next?
The line value = a + rest is run next. In this run of the function, a = 3 and rest = 0 so now value = 3.
What happens next?
The line return value is run. This returns 3 from the function. This also exits from the run of the function mult(3, 1). After return is called, we go back to running mult(3, 2).
Where were we in mult(3, 2)?
We had the variables a = 3 and b = 2 and were examining the line rest = mult(a, b - 1).
So what happens now?
The variable rest get 3 assigned to it. The next line value = a + rest sets value to 3 + 3 or 6.
So now what happens?
The next line runs, this returns 6 from the function. We are now back to running the line print("3 * 2 = ", mult(3, 2)) which can now print out the 6.
What is happening overall?
Basically we used two facts to calculate the multiple of the two numbers. The first is that any number times 0 is 0 (x * 0 = 0). The second is that a number times another number is equal to the first number plus the first number times one less than the second number (x * y = x + x * (y - 1)). So what happens is 3 * 2 is first converted into 3 + 3 * 1. Then 3 * 1 is converted into 3 + 3 * 0. Then we know that any number times 0 is 0 so 3 * 0 is 0. Then we can calculate that 3 + 3 * 0 is 3 + 0 which is 3. Now we know what 3 * 1 is so we can calculate that 3 + 3 * 1 is 3 + 3 which is 6.

This is how the whole thing works:

3 * 2
3 + 3 * 1
3 + 3 + 3 * 0
3 + 3 + 0
3 + 3
6

Recursion

Programming constructs solving a problem by solving a smaller version of the same problem are called recursive. In the examples in this chapter, recursion is realized by defining a function calling itself. This facilitates implementing solutions to programming tasks as it may be sufficient to consider the next step of a problem instead of the whole problem at once. It is also useful as it allows to express some mathematical concepts with straightforward, easy to read code.

Any problem that can be solved with recursion could be re-implemented with loops. Using the latter usually results in better performance. However equivalent implementations using loops are usually harder to get done correctly.

Probably the most intuitive definition of recursion is:

Recursion
If you still don't get it, see recursion.

Try walking through the factorial example if the multiplication example did not make sense.

Examples

factorial.py

#defines a function that calculates the factorial
 
def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)
 
print("2! =", factorial(2))
print("3! =", factorial(3))
print("4! =", factorial(4))
print("5! =", factorial(5))

Output:

2! = 2
3! = 6
4! = 24
5! = 120

countdown.py

def count_down(n):
    print(n)
    if n > 0:
        return count_down(n-1)
 
count_down(5)

Output:

5
4
3
2
1
0


Learning Python 3 with the Linkbot
 ← Defining Functions Advanced Functions Example Lists →