- Using the polling method to check for the pizza delivery wasn't very efficient. And it was pretty exhausting running up and down those stairs. Luckily, there's a better way to wait for the pizza guy. Event-driven programming. I can just lay here on the bed, waiting for an event to occur. And when that event finally does occur, it'll interrupt my waiting state and only then will I have to get up off the bed to handle that event with the appropriate action. There are a lot of different types of events that I'm prepared to handle. One example is a timer event. The pizza company promises that they'll deliver the pizza in 30 minutes or less.
And I've decided to give them a few minutes of slack so I've set a timer for 45 minutes. And if I haven't received my pizza by then, I should probably give them a call to see if there's a problem with the order. (phone rings) Now that the timer event has interrupted my waiting state, I should handle it like I said I would by giving the pizza company a call. Hi, I'm still waiting on that pizza. Ah, okay, see you soon.
Stuck in traffic. Well, now that I'm done handling that timer event, I can return back to my waiting state. In addition to using timers to trigger events, I can also respond to user-generated events like a button press. (doorbell rings) Ah, the doorbell! That must be the pizza. Now, I'll handle that event by going to get the pizza.
So I'll go down the stairs, but only once this time. And now I can meet the pizza guy. (phone rings) Oh, I hear my phone ringing upstairs. Normally, I'd answer my phone right away, but I'm in the middle of handling this doorbell ringing event. As an event-driven program, I can only handle one event at a time, and I'll do so in the order that those events occur. That means it's very important that I don't take too long to answer the door, otherwise I might miss that phone call. So, without further ado. Hey, you made it.
Here, keep the change. And now I can answer the phone. Hey Bern, how's it going? This is the Python script called start_12_01_alarms_and_doorbells, which is a brief study in handling life events. I start by importing a module called Tkinter, which is the standard Python module for creating graphical user interfaces. I'm choosing to use Tkinter here to demonstrate event-driven programming because it's an easy framework to create and handle events.
User interfaces are almost always designed as event-driven programs because their purpose is to respond to user actions. I won't go into details about the behind-the-scenes function of the Tkinter module, because that's a huge topic all on it own. In an event-driven program, I need to specify the code that will get executed when each of the different events occur. These are called event handlers. I've written three functions in this script that'll act as the handlers for the three events. My alarm going off, the doorbell ringing, and my phone ringing upstairs.
When the timer alarm goes off, I'll give the pizza company a call to see where my pizza is. If the doorbell rings, I need to go open it. And when my phone rings, I need to answer the phone. The bottom part of this script uses the Tk constructor method from the Tkinter module to create a new GUI window, which I named root, and then it creates two buttons inside of that window. The first button, which will have the text Ring Doorbell on it, is configured so that when the user clicks on that button, it'll generate an event which will execute the command I specified.
In this case, clicking on Ring Doorbell will execute the doorbell function. I did the same thing to create a second button labeled Call Phone, which will execute the phone call handler function when it's clicked. So, those are my two buttons to trigger user-generated events. The line after that configures the timer event by using Tkinter's after method. When this method gets called, it'll start a timer for 4,000 milliseconds, which is four seconds, and after that time has elapsed it'll generate an event to execute the alarm function.
The very last line of this script is just something that's necessary to kickstart the Tkinter module to make everything run. So let's run this script now. That created the little window with buttons to ring the doorbell or call the phone. And after four seconds have passed, the timer event gets triggered and prints "Calling the Pizza Company" in the shell window. In the scenario earlier, while I waited for my alarm to go off, I went into a waiting state by taking a nap. The same thing is happening in the program because it's also going into a waiting state.
And when the timer runs out, it'll interrupt that waiting state and wake up the program to execute the handler for that timer event. I can also interrupt the waiting state manually by generating events, ringing the doorbell, or calling the phone. When I click on one of those buttons, the Python executes the appropriate handler function and then returns to its waiting state until the next event occurs. So, if I ring the doorbell, as expected I answer the door. And if I click on Call Phone, I answer the phone. Notice that both of those actions resulted in an immediate response.
If I ring the doorbell over and over, my program can answer the door as fast as I'm able to click the button. Similarly, as fast as I'm able to call the phone, I'm also able to answer it. But today is a lazy Saturday and I'm not always quick to respond to things. Let's say, when the doorbell rings, I'm in the middle of watching a TV show and I want to wait until the commercial break to answer the door. To simulate that, I'll insert a delay into the program by importing the time module. And then I'll call the time module sleep function to wait for four seconds after the doorbell rings before I go to open it.
I'll save and run that script. So now, when I ring the doorbell, it takes four seconds after the doorbell handler prints "Ding Dong!" for the program to get past the sleep function and print out "Opening the Door". If the doorbell is the only event happening in the scenario, then yes, it's kind of annoying to the person downstairs that they have to wait for me to open the door. But it's not the end of the world. But what happens if right after the doorbell rings, somebody gives me a phone call? Well, I'll demonstrate that now by ringing the doorbell and then immediately calling the phone.
The program won't answer the phone until after I open the door. Let me do that one more time. I'll ring the doorbell, and while the doorbell function is stuck waiting, I'll place a phone call. That phone call doesn't get answered until after I've completely finished handling the doorbell event. This is because Tkinter can only handle one event at a time, and it'll do so in the order that those events occur. So when the doorbell event occurs, the program is in the process of running that doorbell handler. And then when the phone call event occurs, it gets placed into an event queue to be handled as soon as the doorbell event is finished.
I can even put multiple phone call events into the handler queue. So for example, if I ring the doorbell, and then I place five calls really fast, you'll notice that after the doorbell handler is finished, then Tkinter goes and handles those five calls. This is the reason you should design your handler functions to execute in the shortest amount of time possible. If your handler takes too long to execute, you'll be delaying other actions from being handled, and that can create a slow and clunky user interface.
- Reusing functions
- Local vs. global variables
- Creating and naming custom objects
- Class inheritance
- Modules and packages
- Multidimensional lists and tuples
- Queues and stacks
- Creating and combining sets
- Storing data in dictionaries
- If/else and switch statements
- For vs. while loops
- Error handling
- Polling and event-driven programming