07. Modules, Packages, Exception, and Assertion#

  • For the sample code of this chapter, please refer to here.

Last time#

  • Recursion

  • Global variable

  • Dictionaries

  • Functions with multiple input arguments

Today#

Modules#

  • As your program gets larger, you might want to divide them into several files that allows you to manage or maintain them conveniently.

  • To use the variables, functions, etc. in those files, you need to use a special function called import.


Example: nycudopcs2023.py#

  • A self-defined module

    def youfool():
        print("HoHoCheng:")
        print("    You fool!!!")
    
    def gossip(idx=0):
        print("Something is calling me.")
        if idx == 0:
            print("大二的某人:")
            print("    作業量太少,為了使下屆能夠充分學習Python知識,建議作業改成每日一份,且加入上機考,使同學能夠養成規律複習的習慣")
        elif idx == 1:
            print("大二的某人:")
            print("    我覺得作業量有點少,每份homework不用多久就寫完了,希望下一屆可以出20份HW,每週練習2份作業才有充分且實在的體驗到程式的練習,讓coding能力更上一層樓。")
        else:
            print("大二的某人:")
            print("    我覺得下次請給學弟妹更多的作業")
    
    student_id = ["112514001", "112514002", "112514003",]
    
    print("This is nycudopcs.py. __name__:", __name__)
    
# import module "nycudopcs2023"
from scripts import nycudopcs2023

# call function in "nycudopcs2023"
nycudopcs2023.youfool()

# call variables in "nycudopcs2023"
print(nycudopcs2023.student_id)

# call another function in "nycudopcs2023"
for i in range(3):
    nycudopcs2023.gossip(idx=i)
This is nycudopcs.py. __name__: scripts.nycudopcs2023
HoHoCheng:
    You fool!!!
['112514001', '112514002', '112514003']
Something is calling me.
大二的某人:
    作業量太少,為了使下屆能夠充分學習Python知識,建議作業改成每日一份,且加入上機考,使同學能夠養成規律複習的習慣
Something is calling me.
大二的某人:
    我覺得作業量有點少,每份homework不用多久就寫完了,希望下一屆可以出20份HW,每週練習2份作業才有充分且實在的體驗到程式的練習,讓coding能力更上一層樓。
Something is calling me.
大二的某人:
    我覺得下次請給學弟妹更多的作業

Rename your module#

  • For simplicity and convenience, you can rename the module.

# import module "nycudopcs2023" as "dopcs"
from scripts import nycudopcs2023 as dopcs

# call functions in "dopcs"
dopcs.youfool()

# call variables in "dopcs"
print(dopcs.student_id)
HoHoCheng:
    You fool!!!
['112514001', '112514002', '112514003']

Import specific objects from module#

  • You can only import some specific objects from module.

# from module "nycudopcs2023" import "youfool" as "yf"
from scripts.nycudopcs2023 import youfool as yf

# call function "yf"
if __name__ == "__main__":
    yf()
HoHoCheng:
    You fool!!!

Packages#

  • Again, as your program gets larger, you might want to divide them into several files that allows you to manage or maintain them conveniently.

  • This time, we have multiple modules in the same directory. Additional techniques are required for efficient management.

scripts
  ├─ example1                 # "example1" package
  |     ├─ __init__.py        # Initialization of "example1" package
  |     ├─ module1.py         # Module1 in the "example1" package
  |     └─ module2.py         # Module2 in the "example1" package
  |
  ├─ example2
  |     ├─ ...
  |
  ├─ main_example1.py         # Main script 1
  ├─ main_example2.py         # Main script 2
  └─ nycudopcs2023.py
  • The __init__.py file is required to make Python treat directory containing the file as package.


Example: Example1 package#

  • Run this in terminal

    python main_example1.py
    

from scripts.example1.module1 import hello

hello()
哩後!
from scripts.example1 import module1

module1.hello()

print(dir(module1))

print(module1.__name__)

print(module1.__package__)
哩後!
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'hello']
scripts.example1.module1
scripts.example1

Subpackages#

  • Sometime, package consists of several subpackages.

scripts
  ├─ example1                 # "example1" package
  |     ├─ __init__.py        # Initialization of "example1" package
  |     ├─ module1.py         # Module1 in the "example1" package
  |     └─ module2.py         # Module2 in the "example1" package
  |
  ├─ example2                 # "example2" package
  |     ├─ sub1               # "sub1" sub-package
  |     |    ├─ __init__.py   # Initialization of "sub1" sub-package
  |     |    └─ moduleX.py    # ModuleX in the "sub1" sub-package
  |     |
  |     ├─ sub2               # "sub2" sub-package
  |     |    ├─ __init__.py   # Initialization of "sub2" sub-package
  |     |    └─ moduleY.py    # ModuleX in the "sub2" sub-package
  |     |
  |     ├─ __init__.py        # Initialization of "example2" package
  |     ├─ main_sub1.py       # Main script for sub1
  |     └─ main_sub2.py       # Main script for sub2
  |
  ├─ example3                 # "example3" package
  |     ├─ sub1               # "sub1" sub-package
  |     |    ├─ __init__.py   # Initialization of "sub1" sub-package
  |     |    ├─ moduleX.py    # ModuleX in the "sub1" sub-package
  |     |    ├─ moduleY.py    # ModuleY in the "sub1" sub-package
  |     |    └─ moduleZ.py    # ModuleZ in the "sub1" sub-package
  |     |
  |     ├─ sub2               # "sub2" sub-package
  |     |    ├─ __init__.py   # Initialization of "sub2" sub-package
  |     |    ├─ moduleA.py    # ModuleA in the "sub2" sub-package
  |     |    └─ moduleB.py    # ModuleB in the "sub2" sub-package
  |     |
  |     └─ __init__.py        # Initialization of "example3" package
  |
  ├─ main_example1.py         # Main script 1
  ├─ main_example2.py         # Main script 2
  ├─ main_example3.py         # Main script 3
  └─ nycudopcs2023.py

Example: Example2 package#

  • Run this in terminal

python main_example2.py

Example: Example3 package#

  • Run this in terminal

python main_example3.py

Get organization#

  • So far we have learned…

    • Use conditionals to control the flow of program

    • Use loops to complete the routine task

    • Create functions to hide the details and simplify the program

    • Use modules to store different portions of your program

    • Use packages to organize your program

  • All these concepts and tools will help you deal with those computational problems, understand the programs written by other programmers, and most importantly, get yourself organized.

Exception#

  • When we run something like this…

testList = [1, 2, 3]
print(testList[0])
print(testList[3])
print("The End.")
1
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[7], line 3
      1 testList = [1, 2, 3]
      2 print(testList[0])
----> 3 print(testList[3])
      4 print("The End.")

IndexError: list index out of range

Syntax try/except#

  • In python, you can use try/except block to avoid the program terminating itself as it encounters an error.

testList = [1, 2, 3]

try:
    print(testList[0])
    print(testList[3])
except IndexError:
    print("Index out of range!")

print("The End.")
1
Index out of range!
The End.

Common exceptions#

  • AttributeError

  • IndexError

  • IOError

  • NameError

  • SyntaxError

  • TypeError

  • ValueError

  • ZeroDivisionError

Trivia#

  • Signal-to-noise ratio (SNR) is a very important criterion to quantify the performance of a system. In practical, noise always exists in every system, and the noise level might be infinitesimal in some cases.

\[ SNR = 10 \log{\frac{I_{signal}}{I_{noise}}} \]
import math

def snr(sigLevel, nLevel):
    try:
        output = 10 * math.log10(sigLevel/nLevel)
        print("SNR = {:.2f} dB".format(output))
    except ZeroDivisionError:
        print("SNR = infinite")
    except:
        print("Something is going wrong.")

snr(100, 10)
snr(100, 0)

Exceptions as a control flow mechanism#

  • Using exceptions for controlling flow

def getRatios(L1, L2):
    """
    Assumes L1 and L2 are equal length lists of numbers
    Returns a list containing the ratio of L1[i]/L2[i]
    """
    ratio = []

    for idx in range(len(L1)):
        try:
            ratio.append(L1[idx]/L2[idx])
        except ZeroDivisionError:
            ratio.append(float("NaN"))
        except:
            raise ValueError("Something is going wrong!")
    
    return ratio

print(getRatios([9,5,2,7], [9,4,8,7]))
print(getRatios([9,5,2,7], [0,4,8,7]))
print(getRatios([9,5,2,7], [4,8,7]))
try:
    print(getRatios([9,5,2,7], [9,4,8,7]))
    print(getRatios([9,5,2,7], [0,4,8,7]))
    print(getRatios([9,5,2,7], [4,8,7]))
except ValueError as msg:
    print(msg)

Exercise 7.1#

  • Implement a function that meets the specification below. Use a try/except block.

def sumDigits(s):
    """
    Assume s is a string
    Return the sum of the decimal digits in s
    For example, if s is "n994v5", it returns 27
    """

    # 自己想

    return output
  • Test your function with different inputs s, for example, sumDigits("n994v5").


Assertion#

  • An useful tool for confirming the state of computation is as expected.

def findGCD(n1, n2):
    """
    Assumes n1 and n2 are two positive integers
    Returns the greatest common divisor of them
    """
    assert type(n1) == int and type(n2) == int
    
    L, S = max(n1, n2), min(n1, n2)
    
    if L%S == 0:
        return S
    else:
        return findGCD(S, L%S)

print(findGCD(36, 144))
print(findGCD(7, 144))
print(findGCD(3.6, 144))
try:
    print(findGCD(36, 144))
    print(findGCD(7, 144))
    print(findGCD(3.6, 144))
except AssertionError:
    print("Something is going wrong!")
def findGCD(n1, n2):
    """
    Assumes n1 and n2 are two positive integers
    Returns the greatest common divisor of them
    """
    assert type(n1) == int and type(n2) == int, "Variables n1 and n2 must be integers."
    
    L, S = max(n1, n2), min(n1, n2)
    
    if L%S == 0:
        return S
    else:
        return findGCD(S, L%S)

print(findGCD(36, 144))
print(findGCD(7, 144))
print(findGCD(3.6, 144))

Exercise 7.2#

  • Implement a function that meets the specification below. It should pop out an AssertionError instead of TypeError as you running the code.

def findAnOdd(L):
    """
    Assumes L is a list of integers
    Returns the first odd number in L
    Raises ValueError if L does not contain an odd number
    """

    # 自己想

    return output
  • Test your function with different inputs L, for example, findAnOdd([4, "v"]).


Argument parser#

  • You may have a question about this:



  • How?

    • Use argparse module



import argparse
from Pick.PickSys import pickStudent


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="NYCUDOPCS Random Pick System")
    parser.add_argument("--num", default=3, type=int, help="How many student do you want to pick today? (default: 3)")
    parser.add_argument("--csv_dir", default="1121-515401.csv", type=str, help="Directory of CSV file")

    opt = parser.parse_args()

    pickStudent(num=opt.num, path=opt.csv_dir)

Exercise 3: Taylor series of \(\sin\) and \(\cos\)#

  • Please write a function called sin that aims to return you \(sin\) by Taylor series.

\[ \sin{x} = \sum_{k=1}^{n}{(-1)^{k}\frac{x^{2k-1}}{(2k-1)!}} \]
def sin(x, n=100):

    # 自己想

    return output
  • Please write a function called cos that aims to return you \(cos\) by Taylor series.

\[ \cos{x} = \sum_{k=0}^{n}{(-1)^{k}\frac{x^{2k}}{(2k)!}} \]
def cos(x, n=100):

    # 自己想

    return output