Learning Python 3 with the Linkbot/Advanced Functions Example
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()
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
- 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.
- This line detects to see if button B is pressed, and that
eventis true. If
eventis 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.
- The next three lines declare three variables. The
globalkeyword 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.
- If the program ever gets to this point, we know that the player has pressed the button, so we set the global variable
- 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_startwill be false.
- 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
enableButtonEventsfunction. 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.
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.
- Set the LED color to red
- 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.
- 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.
- 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.
- Since the game has now started, we set the global variable
- Start beeping the robot
- Turn the robot's LED green.
- 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.
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
- And what does
- We need to do a walkthrough of the
multfunction to find out.
- What happens next?
- The variable
agets the value 3 assigned to it and the variable
bgets the value 2 assigned to it.
- And then?
- The line
if b == 0:is run. Since
bhas the value 2 this is false so the line
return 0is skipped.
- And what then?
- The line
rest = mult(a, b - 1)is run. This line sets the local variable
restto the value of
mult(a, b - 1). The value of
ais 3 and the value of
bis 2 so the function call is
- So what is the value of
- We will need to run the function
multwith the parameters 3 and 1.
- So what happens next?
- The local variables in the new run of the function are set so that
ahas the value 3 and
bhas the value 1. Since these are local values these do not affect the previous values of
- And then?
bhas 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
ahas the value 3 and
bhas the value 0.
- So what happens next?
- The first line in the function to run is
if b == 0:.
bhas the value 0 so the next line to run is
- And what does the line
- This line returns the value 0 out of the function.
- 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
multwith the parameters 3 and 0. We have finished running
mult(3, 0)and are now back to running
mult(3, 1). The variable
restgets assigned the value 0.
- What line is run next?
- The line
value = a + restis run next. In this run of the function,
a = 3and
rest = 0so now
value = 3.
- What happens next?
- The line
return valueis run. This returns 3 from the function. This also exits from the run of the function
mult(3, 1). After
returnis called, we go back to running
- Where were we in
- We had the variables
a = 3and
b = 2and were examining the line
rest = mult(a, b - 1).
- So what happens now?
- The variable
restget 3 assigned to it. The next line
value = a + restsets
3 + 3or 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 * 2is first converted into
3 + 3 * 1. Then
3 * 1is converted into
3 + 3 * 0. Then we know that any number times 0 is 0 so
3 * 0is 0. Then we can calculate that
3 + 3 * 0is
3 + 0which is
3. Now we know what
3 * 1is so we can calculate that
3 + 3 * 1is
3 + 3which is
This is how the whole thing works:
3 * 2 3 + 3 * 1 3 + 3 + 3 * 0 3 + 3 + 0 3 + 3 6
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:
- If you still don't get it, see recursion.
Try walking through the factorial example if the multiplication example did not make sense.
#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))
2! = 2 3! = 6 4! = 24 5! = 120
def count_down(n): print(n) if n > 0: return count_down(n-1) count_down(5)
5 4 3 2 1 0