From 4e6a69c0380969bade40f55109584401a3a3924e Mon Sep 17 00:00:00 2001 From: ducoterra Date: Sun, 29 Nov 2020 14:46:34 -0500 Subject: [PATCH] day 4 done --- README.md | 9 +- docs/day4.md | 441 ++++++++++++++++++++++++++++++++++++++++++++++++++ docs/index.md | 6 +- mkdocs.yml | 1 + 4 files changed, 455 insertions(+), 2 deletions(-) create mode 100644 docs/day4.md diff --git a/README.md b/README.md index 2a9a177..a15e2e7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,15 @@ # Docs +Converting mov to gif: + ```bash ffmpeg -i in.mov -filter:v "setpts=0.5*PTS" out.gif -ffmpeg -i in.mkv out.mov +``` + +Converting mkv to mp4 with 20Mbit bitrate + +```bash +ffmpeg -i in.mkv -b:v 20M out.mov ``` ```bash diff --git a/docs/day4.md b/docs/day4.md new file mode 100644 index 0000000..6911f48 --- /dev/null +++ b/docs/day4.md @@ -0,0 +1,441 @@ +# Day 4 + +## "While" + +The last section covered the for loop - a useful loop when you have a finite number of things you need to do something with. A grocery list, api data, and application arguments could be massive but will always have a definite size. Let's talk about times when you don't know how many times to loop. + +### Example 1: A Game + +Let's imagine you're coding a checkers app. You might approach it like this: + +1. Game Start +2. Player 1 moves +3. Check if player 2 has no remaining pieces +4. Player 2 moves +5. Check if player 1 has no remaining pieces +6. Player 1 moves +7. Check if player 2 has no remaining pieces +8. Player 2 moves +9. Check if player 1 has no remaining pieces +10. "" +11. "" +12. "" +13. ... + +You'll need to do a lot of the same stuff over and over - that's great for a loop. Since you'll have no idea how long the game will go on you can't use a for loop because there aren't a finite number of things to loop through (although you could simple use [the longest checkers game](http://www.wylliedraughts.com/LongGame.htm#:~:text=The%20longest%20game%20that%20Wyllie,of%203%2Dmove%20restriction%20checkers.) as your starting point I suppose). + +What we need is a loop that run indefinitey until a condition is met - namely that one player wins the game. + +### Example 2: User Input + +Let's build a menu system for a text-based app. + +1. Open your python interpretor by typing `python` and pressing ++enter++ +2. In your python interpretor paste the following: + + ```python + program_choices = ["print", "count", "add", "exit"] + while True: + choice = input(f"Please choose one of the following options: [{', '.join(program_choices)}]: ") + if choice == "print": + print("hello") + elif choice == "count": + print(", ".join(["1","2","3","4","5"])) + elif choice == "add": + num1 = input("first number: ") + num2 = input("second number: ") + try: + print(f"Answer: {int(num1) + int(num2)}") + except ValueError: + print("You did not provide 2 valid numbers") + elif choice == "exit": + break + else: + print("invalid choice!") + ``` + +3. Exit the menu by typing 'exit' +4. Type exit() to exit the python interpretor + +This lets a user select from a variety of options and returns them to the start of the menu upon a selection. If their selection is invalid we tell them it's invalid and return them to the start anyway. + +Since we don't know how many things a user will want to do with our menu we should use a while loop to loop indefinitely. + +Note the line that says `break`. This is how you break out of a loop in python. as soon as you call `break` it will stop the loop. + +### Easy looping + +Now that we've seen a few examples let's run through the core of a while loop: + +1. Type `python` and press ++enter++ to open the python interpretor. +2. We can use a while loop to do something indefinitely. Type the following and press ++enter++ twice: + + ```python + while True: + print("hello") + ``` + +3. Press ++ctrl+c++ to stop the loop +4. We can use a while loop like a for loop (though this isn't recommended - it's too easy to get stuck in an infinite loop). Type the following and press ++enter++ twice: + + ```python + count_to = 10 + start_at = 1 + while start_at <= count_to: + print(start_at) + start_at += 1 + ``` + +5. We can use a while loop to check multiple conditions. Type the following and press ++enter++: + + ```python + import random + day = 1 + raining = True + temperature = "cold" + while temperature == "cold" or raining == True: + print(f"Day {day}: Stay inside") + day += 1 + temperature = random.choice(["cold", "warm"]) + raining = random.choice([True, False]) + + print(f"Day {day}: It's safe") + ``` + +6. We can put while loops inside while loops (notice how we break out of each loop - this requires 2 break statements). Type the following and press ++enter++: + + ```python + while True: + print("loop 1") + while True: + print("loop 2") + break + break + ``` + +## Let's build a menu + +We're going to build a piece of software that lets us interact with our people API. We should be able to: + +1. List the people from our API +2. Update the list by calling the API +3. Clear the list +4. Exit + +Let's get started: + +1. Create a new file in your root directory called "menu.py" +2. Add the following to the top: + + ```python + import requests + import json + ``` + +3. Create the list_people function by adding the following: + + ```python hl_lines="5-7" + import requests + import json + + def list_people(people): + print(people) + return people + ``` + +4. Now create the update_people function by adding the following: + + ```python hl_lines="5-12" + def list_people(people): + print(people) + return people + + def update_people(people): + try: + response = requests.get("http://localhost:8000/people") + people = response.json()["data"] + print("successfully updated people.") + except requests.exceptions.ConnectionError: + print("Server is not running. Failed to update.") + return people + ``` + +5. Now create the clear_people function by adding the following: + + ```python hl_lines="10-12" + def update_people(people): + try: + response = requests.get("http://localhost:8000/people") + people = response.json()["data"] + print("successfully updated people.") + except requests.exceptions.ConnectionError: + print("Server is not running. Failed to update.") + return people + + def clear_people(people): + people = None + return people + ``` + +6. Finally add the main loop: + + ```python hl_lines="5-19" + def clear_people(people): + people = None + return people + + if __name__ == "__main__": + people = None + choices = ["list", "update", "clear", "exit"] + while True: + choice = input(f"Please choose an option [{', '.join(choices)}]: ") + if choice == "list": + people = list_people(people) + elif choice == "update": + people = update_people(people) + elif choice == "clear": + people = clear_people(people) + elif choice == "exit": + break + else: + print("Invalid choice. Please try again.") + ``` + +7. Check your work. Your whole program should be 35 lines long and look like this: + + ```python + import requests + import json + + def list_people(people): + print(people) + return people + + def update_people(people): + try: + response = requests.get("http://localhost:8000/people") + people = response.json()["data"] + print("successfully updated people.") + except requests.exceptions.ConnectionError: + print("Server is not running. Failed to update.") + return people + + def clear_people(people): + people = None + return people + + if __name__ == "__main__": + people = None + choices = ["list", "update", "clear", "exit"] + while True: + choice = input(f"Please choose an option [{', '.join(choices)}]: ") + if choice == "list": + people = list_people(people) + elif choice == "update": + people = update_people(people) + elif choice == "clear": + people = clear_people(people) + elif choice == "exit": + break + else: + print("Invalid choice. Please try again.") + ``` + +8. Run the program by typing `python menu.py` and pressing ++enter++. +9. Type "list" and press ++enter++. You should see "None" printed to the screen. We haven't updated our people yet! +10. Type "update" and press ++enter++. You should see "Server is not running. Failed to update.". We didn't run our server! +11. Open a new terminal by clicking the plus icon. +12. Type `python manage.py runserver` and press ++enter++. +13. Flip back over to your other terminal by using the dropdown menu. +14. Type "update" and press ++enter++. You should see "successfully updated people." +15. Type "list" again and press ++enter++. You should see your people print out. +16. Type "clear" and press ++enter++. +17. Type "list" again and press ++enter++. Your people should be cleared. +18. Type "exit" to exit. +19. Flip back over to your django terminal with the dropdown menu +20. Press ++ctrl+c++ to stop the server + +### Threading + +One of the more difficult concepts in programming is threading and multiprocessing. It's rarely taught at an intro level but it's fairly easy to use. + +A program runs in a "thread". When you run `python menu.py` it creates one thread that executes all code in order. Code at the end of your file can't run before code at the beginning of your file. + +...unless it could. What if you have a super slow internet connection and you need to make an api call? You don't want it to slow down your whole menu. + +Here's the idea: we tell our computer to make the slow call to our API in the background and continue letting the user mess with the menu. + +Let's make that a reality by simulating a slow internet connection. + +1. Open views.py +2. For this part we're going to need the time library. "time" lets us pause code execution for a bit, simulating a slow internet. At the top of views.py add the following: + + ```python hl_lines="2 2" + import random + import time + from django.shortcuts import render + from django.http import JsonResponse + ``` + +3. Jump to the very bottom and add the highlighted code: + + ```python hl_lines="10-23" + def api(request): + people = [ + {"first name" : "Jim", "last name": "Fowler", "age": 24}, + {"first name" : "Bob", "last name": "Jones", "age": 36}, + {"first name" : "Alice", "last name": "Appleseed", "age": 52} + ] + + return JsonResponse({"data": people}) + + def slow_api(request): + people = [] + first_names = ["Liam", "Noah", "Oliver", "William", "Olivia", "Emma", "Ava", "Sophia", "Isabella"] + last_names = ["Smith", "Johnson", "Anderson", "Brown", "Garcia", "Miller", "Martinez", "Chavez"] + num_people = random.randint(1,100) + + for person in range(num_people): + time.sleep(.1) + first_name = random.choice(first_names) + last_name = random.choice(last_names) + age = random.randint(1,100) + people.append({"first_name": first_name, "last_name": last_name, "age": age}) + + return JsonResponse({"data": people}) + ``` + +4. Save with ++ctrl+s++ +5. We need to add a URL, open urls.py +6. Add the highlighted code: + + ```python hl_lines="8 8" + from django.urls import path + from . import views + + urlpatterns = [ + path('', views.index), + path('weather/', views.weather), + path('people/', views.api), + path('slow/', views.slow_api), + ] + ``` + +7. Save with ++ctrl+s++ +8. Open a new terminal by clicking the plus icon. +9. Type `python manage.py runserver` and press ++enter++. +10. Flip back over to your other terminal by using the dropdown menu. +11. Navigate to to see your api results. Notice how long the page takes to load. +12. Let's try our menu.py with the new API. Open menu.py and update line 10: + + ```python hl_lines="3 3" + def update_people(people): + try: + response = requests.get("http://localhost:8000/slow") + people = response.json()["data"] + print("successfully updated people.") + ``` + +13. Save with ++ctrl+s++ +14. Open a new terminal by clicking the plus icon +15. Type `python menu.py` and press ++enter++ +16. Type "list" and press ++enter++. You should see `None` +17. Type "update" and press ++enter++. Notice the delay. While we're waiting for our api to respond our menu won't respond to any thing we type. +18. Type "exit" to exit. +19. Let's add the magic that will unblock our menu during an API call. At the top of menu.py add the following: + + ```python hl_lines="3 3" + import requests + import json + import concurrent.futures import ThreadPoolExecutor + ``` + +20. We need to modify our main program to call the update_people function in a different thread. To do this we need to keep track of a "future" object. + + A future is something that hasn't finished executing yet. We don't know when it will finish but we assume that it will. + + ```python hl_lines="3 8 10" + if __name__ == "__main__": + people = None + future = None + choices = ["list", "update", "clear", "exit"] + while True: + choice = input(f"Please choose an option [{', '.join(choices)}]: ") + if choice == "list": + people = list_people(future, people) + elif choice == "update": + future = ThreadPoolExecutor().submit(update_people, people) + elif choice == "clear": + people = clear_people(people) + elif choice == "exit": + break + else: + print("Invalid choice. Please try again.") + ``` + +21. We need to modify the `list_people` function to handle a future. This ensures that new people will print after an update. Change the following: + + ```python hl_lines="1-4" + def list_people(future, people): + if future is not None and future.done(): + people = future.result() + future = None + print(people) + return people + ``` + +22. Save with ++ctrl+s++ +23. Your menu.py should look like this: + + ```python + import requests + import json + from concurrent.futures import ThreadPoolExecutor + + def list_people(future, people): + if future is not None and future.done(): + people = future.result() + future = None + print(people) + return people + + def update_people(people): + try: + response = requests.get("http://localhost:8000/slow") + people = response.json()["data"] + print("successfully updated people.") + except requests.exceptions.ConnectionError: + print("Server is not running. Failed to update.") + return people + + def clear_people(people): + people = None + return people + + if __name__ == "__main__": + people = None + future = None + choices = ["list", "update", "clear", "exit"] + while True: + choice = input(f"Please choose an option [{', '.join(choices)}]: ") + if choice == "list": + people = list_people(future, people) + elif choice == "update": + future = ThreadPoolExecutor().submit(update_people, people) + elif choice == "clear": + people = clear_people(people) + elif choice == "exit": + break + else: + print("Invalid choice. Please try again.") + ``` + +24. Run +25. Now run `python menu.py` +26. Type `update` and press ++enter++. Notice the menu returns instantly. +27. In a moment you'll see "successfully updated people" print. Type `list` and press ++enter++. +28. Type `clear` and press ++enter++ +29. Now type `update` and press ++enter++ and quickly type `list` and press ++enter++ before it updates. Notice you can interact with the menu before the result returns! +30. You've successfully written a multithreaded program. Type `exit` to exit. +31. Flip back to your django server terminal with the dropdown +32. Press ++ctrl+c++ to stop the server. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index abcbd55..09db7fa 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,4 +27,8 @@ - "For" - Creating an API -- Reading an API with Python \ No newline at end of file +- Reading an API with Python + +### [Day 4](day4.md): while + +- "While" \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 94766e1..26067b7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,6 +5,7 @@ nav: - Day 1: day1.md - Day 2: day2.md - Day 3: day3.md + - Day 4: day4.md theme: name: material markdown_extensions: