Files
2020-12-08 19:43:39 -05:00

14 KiB

Day 5

"import"

In our last section we're going to talk about organization. Not a glamorous topic, but an important one nonetheless!

What an import actually is

You've already done this quite a bit - but here's a recap

  1. Type python in the terminal and press ++enter++ to start the interpretor
  2. Type requests.get("https://google.com") and press ++enter++
  3. You should see NameError: name 'requests' is not defined printed to the terminal
  4. Type import requests and press ++enter++
  5. Now type requests.get("https://google.com") again and press ++enter++
  6. You should see <Response [200]> printed to the terminal. You've just imported a module.
  7. Type exit() and press ++enter++ to exit

"importing" alerts python that you want to use some code that isn't in the file you're currently working in. But where is "requests"? Remember that "venv" folder we made? Look inside it:

requests

Requests is just a bit of python code that lives in our venv. In fact, we can rename it and import it as something completely different (though this isn't recommended). Give this a try:

  1. Rename the requests folder in your venv to "somethingelse"
  2. Type python in the terminal and press ++enter++ to open the interpretor
  3. Type import somethingelse and press ++enter++
  4. Type somethingelse.get("https://google.com") and press ++enter++. You should see <Response [200]> printed to the terminal. Your rename worked! You just changed the name python references when we import the code.
  5. Type exit() and press ++enter++ to exit.
  6. Rename "somethingelse" back to " for now.

Using "as" to rename imports

Sometimes we don't want to use the name of the package in our code. It might conflict with something we already named, it might be really long to type, or it might just be poorly named. Fortunately, we don't have to rename the folder to change the name.

  1. Type python in the terminal and press ++enter++ to open the interpretor
  2. Type import requests as somethingelse and press enter
  3. Type somethingelse.get("https://google.com") and press ++enter++. You should see <Response [200]> printed to the terminal. You can use as to rename modules.
  4. Type import requests and press ++enter++
  5. Type requests.get("https://google.com") and press ++enter++. You should see <Response [200]> printed to the terminal. You can import requests like normal alongside your custom name.
  6. Type exit() and press ++enter++ to exit

dots

When you type requests.get() have you considered what the . means? Anytime you see a . it means you can split that thing apart. Example:

requests . get
└──────┘ ^ └─┘

are two separate things.

requests is a "package". A package can container other packages, classes, functions, variables, etc.

get is a "function". That means it was created like this:

def get(url, ...):
    *code here*

Check around line 64 in venv/lib/python3/site-packages/requests/api.py to see the actual function.

Let's actually make use of that dot.

  1. Type python in the terminal and press ++enter++
  2. Type from requests import get and press ++enter++
  3. Type requests.get("https://google.com") and press ++enter++
  4. You should see NameError: name 'requests' is not defined printed to the terminal
  5. Type get("https://google.com") and press ++enter++. You should see <Response [200]> printed to the terminal. You just split the get function from the requests package. You don't need to type requests. to use the get function.
  6. Type post("https://google.com") and press ++enter++. You should see NameError: name 'post' is not defined printed to the terminal. post is a valid function, let's import it.
  7. Type from requests import * and press ++enter++. We just used a "wildcard" import. It import everything into our interpretor from the package..
  8. Type post("https://google.com") and press ++enter++. You should see <Response [405]> printed to the terminal. You didn't need to from requests import post to use the post function. You imported it when you used the * symbol.
  9. Type exit() and press ++enter++ to exit.

Breaking apart our terrible weather app

What better way to demonstrate the power of imports than with our terrible weather app?

  1. Type python weather_app.py in your terminal and press ++enter++. You should run through your terrible weather app just like on day 2

  2. Open "weather_app.py" in VSCode

  3. Notice the import random at the top, you should know what's happening here.

  4. Create a new file called weather_config.py in the root directory

    config

  5. Copy the 4 variables from the top of "weather_app.py" into "weather_config.py"

    copy

  6. Save with ++ctrl+s++

  7. Delete those variables from "weather_app.py".

    delete

  8. Save with ++ctrl+s++

  9. Delete import random from the top of "weather_app.py"

  10. Save with ++ctrl+s++

  11. Add import random to the top of "weather_config.py"

    import random
    
    warm = random.choice([True, False])
    cold = not warm
    raining = random.choice([True, False])
    snowing = not raining
    
  12. Save with ++ctrl+s++

  13. In your terminal type python weather_app.py and press ++enter++. You should see NameError: name 'warm' is not defined print to the terminal. But warm is in our weather_config.py! We need to import it!

  14. At the top of "weather_app.py" add from weather_config import warm

    from weather_config import warm
    
    if warm or cold:
        print("It's warm or cold.")
    
    if raining or warm:
    
  15. Save with ++ctrl+s++

  16. Type python weather_app.py again and press ++enter++. This time you should see NameError: name 'cold' is not defined printed to your terminal. Ah, we imported warm but none of the other variables. We need to import those as well. We could type everything out one by one, but instead:

  17. At the top of "weather_app.py" change from weather_config import warm to from weather_config import *

  18. Save with ++ctrl+s++

  19. Now type python weather_app.py again and press ++enter++. Your weather app works as normal!

Breaking it even further apart

We're going to turn each "chunk" of code in our weather app into a function that we can move to another file.

  1. Starting at line 3, add the following (indent the if statements!):

    def print_clues():
    --->if warm or cold:
    --->--->print("It's warm or cold.")
    
    --->if raining or warm:
    --->--->print("It's raining or warm.")
    
    --->if raining or snowing:
    --->--->print("It's raining or snowing.")
    
    --->if cold or snowing:
    --->--->print("It's cold or snowing.")  
    

    clues

  2. Now do the same to our "guess chunks" like so:

    def check_guesses():
    --->warm_guess = input("Is it warm? (y/n) ")
    --->if warm_guess == 'y' and warm:
    --->--->print('Correct!')
    --->elif warm_guess == 'n' and not warm:
    --->--->print('Correct!')
    --->else:
    --->--->print('Wrong!')
    
    --->cold_guess = input("Is it cold? (y/n) ")
    --->if cold_guess == 'y' and cold:
    --->--->print('Correct!')
    --->elif cold_guess == 'n' and not cold:
    --->--->print('Correct!')
    --->else:
    --->--->print('Wrong!')
    
    --->raining_guess = input("Is it raining? (y/n) ")
    --->if raining_guess == 'y' and raining:
    --->--->print('Correct!')
    --->elif raining_guess == 'n' and not raining:
    --->--->print('Correct!')
    --->else:
    --->--->print('Wrong!')
    
    --->snowing_guess = input("Is it snowing? (y/n) ")
    --->if snowing_guess == 'y' and snowing:
    --->--->print('Correct!')
    --->elif snowing_guess == 'n' and not snowing:
    --->--->print('Correct!')
    --->else:
    --->--->print('Wrong!')
    

    guesses

  3. Save with ++ctrl+s++

  4. Type python weather_app.py in the terminal and press ++enter++. Nothing happens!

  5. Create a new file called "weather_run.py" in your root directory.

    run

  6. Add the following to "weather_run.py"

    from weather_app import print_clues, check_guesses
    
    print_clues()
    check_guesses()
    
  7. Save with ++ctrl+s++

  8. In your terminal type python weather_run.py and press ++enter++. Your weather app works again! You've broken it out into a bunch of modules.

  9. Type python in your terminal and press ++enter++ to open the python interpretor

  10. Type import weather_run and press ++enter++. Your weather app should run in the terminal

  11. Press ++ctrl+c++ to stop your app from running

  12. Type exit() to exit

  13. We don't want your app to run when we import it. Let's add a check to "weather_run.py" to make sure that doesn't happen. Modify "weather_run.py" like so:

    from weather_app import print_clues, check_guesses
    
    if __name__ == "__main__":
    --->print_clues()
    --->check_guesses()
    

    main

  14. Type python in your terminal and press ++enter++ to open the python interpretor

  15. Type import weather_run and press ++enter++. This time nothing should happen! That's good, we want to import our app without it running immediately. Just like importing requests doesn't immediately run the get function, we don't want anything to run on import of our programs.

  16. Type exit() to exit

Importing our menu

We can use imports to make our lives a lot easier. Let's use our menu as an example.

  1. Open "menu.py" in VSCode
  2. Type python in your terminal and press ++enter++ to open the python interpretor
  3. Type import menu and press enter. You've just imported all our functions from "menu.py". Notice how the menu doesn't run though, that's because we added a if __name__ == "__main__" block! We were thinking ahead.
  4. Open a new terminal windows by clicking the plus icon
  5. Type python manage.py runserver and press ++enter++ to start your server
  6. Use the dropdown to switch back to your other terminal window
  7. Type menu.update_people(None) and press ++enter++. You should see people print to the terminal. But wait, that's not what update() was supposed to do - and why did we have to specify (None)?

If we want our menu functions to be useful outside our menu app we have some work to do:

  1. First, I shouldn't have to pass a default value like (None) to our functions. Edit list_people like so:

    def list_people(future=None, people=None):
        if future is not None and future.done():
            people = future.result()
            future = None
        print(people)
        return (future, people)
    

    This specifies that unless a "future" or "people" is provided they are "None" by default.

  2. Let's do that to our remaining functions:

    def update_people(people=None):
        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=None):
        people = None
        return people
    
  3. Now let's fix our update function. update_people is supposed to run in the background, but it just returned the list of people. Let's create a "decorator".

  4. At the top of menu.py add the following:

    import requests
    import json
    from concurrent.futures import ThreadPoolExecutor
    
    def run_in_background(function):
        def wrapper(*args, **kwargs):
            return ThreadPoolExecutor().submit(function, *args, **kwargs)
        return wrapper
    
    def list_people(future, people):
        if future is not None and future.done():
            people = future.result()
            future = None
    
  5. Now add the following above update_people():

    @run_in_background
    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
    
  6. Remove the ThreadPoolExecutor() part at line 40:

    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":
                future, people = list_people(future, people)
            elif choice == "update":
                future = update_people(people)
            elif choice == "clear":
                people = clear_people(people)
            elif choice == "exit":
                break
            else:
                print("Invalid choice. Please try again.")
    
  7. Let's make sure we didn't break our menu. Type python menu.py and press ++enter++

  8. Type update, list, clear, and exit to test your program.

  9. Type python and press ++enter++ to open the interpretor

  10. Type import menu and press ++enter++

  11. Type people = menu.list_people() and press ++enter++. You should see None print.

  12. Now type update = menu.update_people() and press ++enter++. Nothing should print.

  13. Wait a moment for "successfully updated people." to print to the terminal

  14. Type people = menu.list_people(update). Your people should list! You successfully turned your menu into an importable package!

  15. Type exit() to exit.

Using our imported menu

For our last trick we'll use our menu functions in a new program.

  1. Create a new file named "print_people.py"

    print_people

  2. Add the following:

    from menu import update_people, list_people
    from concurrent.futures import wait
    
    update = update_people()
    wait([update])
    list_people(update)
    
  3. Type python print_people.py and press ++enter++ to run your program. You should see your people print after a while.

Congratulations! You've just imported a program you wrote to communciate with an API and used it to do something automatically.