Configuring a Folder to Run as a Program

Attention Students: this file is strictly supplemental. You are in no way expected to read any of it, and none of the following information is necessary for you to complete the assignment! This document exists to explain how this assignment works, and why it is structured the way it is.

Some of the things we have done in this assignment are non-standard and — if you continue reading — you will come to learn that this is in part due to the fact that Python has room for interpretation in terms of what is “right”.

A Refresher on Imports

Throughout the course you have learned about various aspects of the Python packaging system, in particular how to import functions and classes from other files. In the world of python, we refer to the file as a “module”. In this course, we have restricted our use of modules to be in the same directory as one another. For example, consider the directory structure, where all methods in second.py return a str:

directory/
    first.py
        - def print_message()
    second.py
        - def hello()
        - def there()
        - def beautiful()
        - def world()

What we have done so far in the course, would be to in first.py, do something like

# Option 1: import everything
from second import *

def print_message():
    """ Prints a welcome message. """
    message = hello() + there() + beautiful() + world()
    print(message)

as we know, we can also import specific functions. In the following example, we will not import the function beautiful(), and instead implement our own.

# Option 2: import specific functions
from second import hello, there, world

def beautiful():
    """ Returns the string ``"BEAUTIFUL"``. """
    return "BEAUTIFUL"

def print_message():
    """ Prints a welcome message. """
    message = hello() + there() + beautiful() + world()
    print(message)

The natural question being “what if I want to be able to use both beautiful() functions in my code?” We have a few different options, the one you are most familiar with is to instead import just the module:

# Option 3.1
import second

def beautiful():
    """ Returns the string ``"BEAUTIFUL"``. """
    return "BEAUTIFUL"

def print_message(use_first):
    """
    Prints a welcome message.

    :Parameters:
        ``use_first`` (bool)
            If ``True``, use the ``beautiful()`` defined in this module.  Otherwise,
            use the ``beautiful()`` function defined in the ``second`` module.
    """
    if use_first:
        message = second.hello() + second.there() + beautiful() + second.world()
    else:
        message = second.hello() + second.there() + second.beautiful() + second.world()
    print(message)

Using the as keyword, though, we can also import the second.beautiful function as a different name so that we can use either method without issue:

# Option 3.2
from second import hello, there, world
from second import beautiful as other_beautiful

def beautiful():
    """ Returns the string ``"BEAUTIFUL"``. """
    return "BEAUTIFUL"

def print_message(use_first):
    """
    Prints a welcome message.

    :Parameters:
        ``use_first`` (bool)
            If ``True``, use the ``beautiful()`` defined in this module.  Otherwise,
            use the ``beautiful()`` function defined in the ``second`` module.
    """
    if use_first:
        message = hello() + there() + beautiful() + world()
    else:
        message = hello() + there() + other_beautiful() + world()
    print(message)

Wow! This is one of the most useful features of Python: it’s import system is exceptionally flexible! However, you may notice that the following will cause a “problem”:

# Which beautiful will be used?
from second import *

# At this point we have a function ``beautiful()`` in the global namespace, which is
# defined in the ``second`` module.

def beautiful():
    """ Returns the string ``"BEAUTIFUL"``. """
    return "BEAUTIFUL"

# Uh-Oh!  We now have redefined the ``beautiful()`` function _for this file_.  So
# when we call ``beautiful()`` below, it will use the *latest* definition.

def print_message():
    """ Prints a welcome message. """
    message = hello() + there() + beautiful() + world()
    print(message)

If that wasn’t enough, if you moved the import statement to below the definition of the beautiful() function in this file, we will now use the second.beautiful() function since it was brought in last.

Tip

The first takeaway from all of this: Python is flexible and allows you to do many different styles of imports for a reason. Make sure you understand what your import statements are doing, and what side-effects they can have.

Note

In the wild, many people will use from <some module> import * to gain access to things they need. As we can see from above, this is not wrong. But it can lead to hard-to-find bugs when two modules define the same function. We advise exercising extreme caution when using import *, or better — avoid using it altogether!

Importing from Different Directories

Everything we have discussed up until this point, though, has been when the two files are next to each other in the same directory. So now let us consider a slightly different directory structure:

directory/
    dirA/
        first.py
            - def print_message()
    dirB/
        second.py
            - def hello()
            - def there()
            - def beautiful()
            - def world()
        third.py
            - def what_a_wonderful_world()

The first.py and second.py files are no longer next to each other, so currently we cannot import any functions between them. The Python packaging system, in all honesty, is rather complex. This is in part due to the flexibility of import statements we discussed above, but there is also another important factor: which directory are you running the file from?

Recalling that .. means “one directory up”, you may have heard that Python 2 lets you do “Relative Imports”. So you’re first thought may be to try and use something like from ..dirB import second. This will work if your current directory is dirA. But here is the distinction: when you run a file, it inherits the current working directory of the shell that runs it.

# Go to the dirA directory
$ cd directory/dirA

# Run our file
$ python first.py

In this scenario, the working directory when we ran first.py was dirA, so the relative import would have worked. However, if instead we were one directory above:

# Go to the directory
$ cd directory

# Run our file
$ python dirA/first.py

We now have a problem: the current working directory was directory, and first.py tried to import ../dirB. But there is no ../dirB, because we were already in the parent directory!

Warning

Relative imports are an interesting trick, but will likely only cause you grief and peril. There are a lot of reasons to use them, but they are well beyond the scope of this course. As a rule of thumb, avoid them altogether.

The Main File

__main__.py

The __main__ module for MacPan

This is the module with the script code to start up the App. Make sure that this module is in a folder with the following files:

File Purpose
controller.py The primary controller class.
model.py The model classes.
view Directory with view classes.

Moving any of these folders or files will prevent the game from working properly. You are free to add new files into these folders as you wish.

Assignment Specific Workaround

This module provides one method (notify_course_of_action()), which serves to give a slightly more helpful error message when a student tries to complete this assignment with an incompatible version of Python installed.

Often times in the wild you will encounter long and complicated __main__.py (as well as more often, long and complicated __init__.py files). We were concerned about students being able to run the assignment, but did not want to detract from what __main__.py was actually doing. That is, the core mechanics of __main__.py are relatively straightforward:

  1. Import the relevant utilities from the modules in the assignment.
  2. Run the main method.

However, it seemed likely that some students may not have had the right version of Anaconda Python. Depending on when you installed it, there were certain crucial updates related to the core library we used for displaying the assignment. We elected to separate this out into a different file so that when you read __main__.py you would be able to see more clearly what it is doing.

In short, this is a long way of saying the use of error_out.py is non-standard, and only exists due to the circumstances surrounding this particular assignment’s code-base.

error_out.notify_course_of_action()[source]

This method checks to see what version of python is installed, and informs the user where to go to get the correct version of python for this semester.

If this code-base is used in the future, the default library will be PyQt5, so

  1. Switch the imports for PyQt4 from __main__.py and PyQt5 from below.
  2. Update the semester variable, and install_base if the website layout has changed to a new setup.
  3. Update the anaconda_needs variable for the correct version.

Refer to the PyQt documentation for more information on switching to PyQt5.

Return:
int

Always returns 1 (indicates error). Intended use: sys.exit(notify_course_of_action()).