class: big, middle # Engineering 1020: Introduction to Programming .title[ .lecture[Lecture \17\:] .title[Modules] ] .footer[[/lecture/17/](/lecture/17/)] --- # Previously ### Functions (with a bit more structure): * Definition and call syntax * Parameters and arguments * Variable _scope_ * Recursion --- # Today ### Modules * Python files as scripts **(or not)** * The `import` statement * Python scripts and `__main__` --- # Python code quantities ### Often measured in SLoC (_source lines of code_) -- * labs: maybe as much as 200 lines? -- * `engi1020`: 1031 lines -- * [qutebrowser](https://qutebrowser.org): 40k lines -- * Python standard library: 500k lines -- * no limit to amount of code you can write! ??? Large, complex pieces of software like Web browsers and operating systems have **millions** of lines of code. We don't write all of those lines in one source file! --- # Recall: organizing your writing ### How would you organize: * a text? * a letter? * an essay? * a report? * a book? --- # Organizing Python ### We've seen: -- * expressions (like phrases) -- * statements (like sentences) -- - "simple" statements: assignment, `pass`, `return`, ... -- - "compound" statements: `if`, `while`, `for`, `def`, ... ??? "Simple" and "compounds" statements are a distinction that's made by the [full Python grammar](https://docs.python.org/3/reference/grammar.html), which is the "last word" on the syntactic rules of the langauge. However, you're definitely not expected to understand all of these grammatical rules after finishing just one introductory course! -- * functions (like paragraphs) --- # Today ### Modules * Like _chapters_ and _parts_ -- * Organize larger chunks of code into hierarchies -- * Multiple files working together -- * The `import` statement -- (in more detail / with more background than we've seen before!) --- # Python files -- ### So far, we've written Python _scripts:_ * files containing statements * statements executed one at a time -- ### Can also use Python files as _modules:_ -- * files containing statements * statements executed one at time --- # Python files ### So far, we've written Python _scripts:_ * files containing statements * statements executed one at a time ### Can also use Python files as _modules:_ .floatright[_**What's the difference?**_] * files containing statements * statements executed one at time --- # How we run Python code ### Python scripts: -- * open a file -- * click "Run" (or run from the command line) -- ### Python modules * run when we _import_ them -- * "import"... where have we seen that before? --- # `import` statement -- ### Makes names from a module available for use: -- ```python from math import * y = sin(0) ``` -- ```python import math y = math.sin(0) ``` -- ```python import central m = central.mean([1, 2, 3]) ``` -- But how does this actually work? --- # Importing modules ### When we `import` a module: -- * Python _interpreter_ looks for a file with that name + `'.py'` ??? Where the Python interpreter finds a module can be a bit complicated. One set of places it looks is the list of directories contained in `sys.path`: ```python >>> import sys >>> print(sys.path) ['/Users/jon/Documents/Teaching/1020/website/content/lectures/17', '/Applications/Thonny.app/Contents/Frameworks/Python.framework/Versions/3.7/lib/python37.zip', '/Applications/Thonny.app/Contents/Frameworks/Python.framework/Versions/3.7/lib/python3.7', '/Applications/Thonny.app/Contents/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload', '/Users/jon/Library/Python/3.7/lib/python/site-packages', '/Applications/Thonny.app/Contents/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages'] ``` For our purposes, the most important of these is the first entry: the **current directory**. -- * interpreter _executes_ its statements -- * result: _module_ with global names accessible with `.` -- ```python import math y = math.sin(x) z = math.cos(y) ``` --- # `import` syntax -- ```python import goodstuff goodstuff.greet("Jon") ``` -- ```python import goodstuff as stuff stuff.boots_filled = True ``` -- ```python from goodstuff import scald if scald: print("Got 'er scald!") ``` -- ```python from goodstuff import greet as hello hello("world") ``` --- # Aside `dir` tells us names in a module (or other things we'll see later): -- ```python >>> import engi1020.arduino.api >>> dir(engi1020.arduino.api) [#... 'analog_read', 'analog_write', 'buzzer_frequency', #... 'temp_humid_getTemp'] ``` --- # Python modules ### So how can we write/use our own modules? -- 1. Write a Python file (just like we've been doing) -- 2. Save it with a _valid identifer_ name + `.py` -- 3. `import` it from a script _in the same directory_ -- 4. Refer to its _attributes_ (global names) --- # Python modules that _are_ scripts ### One potential problem: ```python def add(x, y): return x + y # Some test code: test_x = input('x?') test_y = input('y?') result = add(test_x, test_y) ``` -- ## What's the problem? ??? If you submit something like this to Gradescope as an assignment, the autograder will try to `import` your code and it will **hang forever** waiting for user input. A module should not do this kind of computation **when imported**. So what can we do instead if we want to write test code (which is a good idea)? --- # Separating test code ### Separation of concerns Separate modules for code that does different things: `central.py` : implementation of [assignment 2](/assignment/2) (**individual**) `test.py` : _tests_ for assignment 2 (can be **shared**) -- ```python import central result = central.mean([1, 2, 3]) expected = 2.0 if result != expected: print("mean returned '" + result + "'; expected '" + expected + "'") ``` --- # Python module `__name__` ### A special variable containing the module's name -- ### In scripts: will be `'__main__'` -- ```python def add(x, y): return x + y if __name__ == '__main__': # Run the tests: test_x = input('x?') # etc. ``` --- # Summary ### Modules * Python files as scripts **(or not)** * The `import` statement * Python scripts and `__main__` --- class: big, middle (here endeth the lesson)