Lab04 - Modules

  1. Modules
  2. __name__ == '__main__'
  3. Module-Level Attributes
  4. docstring
  5. Namespace
  6. Reload a Module
  7. inspect.getsource
  8. Module Search Path

Modules

  1. A module is a file containing Python definitions and statements.
  2. The file name is the module name with the suffix .py appended.
  3. You may put definitions of variables, functions, and classes in a module.
  4. Frequently used functions are usually stored in modules for easier maintenance, and most importantly, easier re-use.
  5. Use your favorite editor to create a Python script:
    # myMath.py
    PI = 3.14159
    
    def add(a, b):
        return a + b
    
    def test_pi():
        print(PI)
  6. Then in a Python interpreter, you may
    >>> import myMath
    >>> print(myMath.PI, myMath.add(3, 5))
  7. It is possible to import a specific name from a module, and use that name directly:
    >>> from myMath import PI
    >>> print(PI)
  8. It is also possible to rename an imported function or variable:
    >>> from myMath import add as addition
    >>>print(addition(3, 7))
  9. Some books show examples to import everything from a module by "from module_name import *". Don't do that. It is easy to cause conflictions.

__name__ == '__main__'

  1. When a module is run as a Python script, the value of the global variable "__name__" is "__main__".
  2. When the file is imported as a module, the value of "__name__" is the module’s name (as a string).
  3. Combined with the if-else statement, we can conveniently write a code to test your functions in a script, but the test will not be performed when you import the module.
  4. Suppose you have a file:
    # myMath2.py
    def gcd(a, b):
        if a == 0:
            return b
        else:
            return gcd(b % a, a)
    
    print(f'[TEST] gcd(48, 36) = {gcd(48, 36)}')
  5. When you run "python3 myMath2.py", it shows "[TEST] gcd(48, 36) = 12". This is good.
  6. When you "import myMath2" in Python, it also shows "[TEST] gcd(48, 36) = 12". This is not good, especially when you have multiple functions to test which takes a while.
  7. Now change the statement to
    if __name__ == "__main__":
        print(f'[TEST] gcd(48, 36) = {gcd(48, 36)}')
  8. Try to run it as a script or import it as a module to see the difference.

Module-Level Attributes

  1. Module-level attributes are variables, functions, classes, or other objects that are defined at the top level of a Python module - outside of any function, class, or conditional (if-else) block.
  2. Within the module, they're accessible to any function or class defined in that module
  3. Outside that module, they can be accessed via "dot notation" (module.attribute) after the module is imported.
  4. For example,
    import myMath
    myMath.test_pi()
    print(myMath.PI)
  5. There are some built-in module-level attributes:

docstring

  1. A docstring is a string which appears as the first statement in a module, class, function.
    # myMath3.py
    'This is my math module.'
    PI = 3.14159
    
    def add(a, b):
        '''Adding two numbers
    
    add(a, b)
        Return the summation of a and b.'''
        return a + b
    
    def test_pi():
        "Testing function to show the value of PI"
        print(PI)
  2. Now try the following statements:
  3. dir(myMath3) shows the attributes of the module "myMath3".
  4. With __doc__ and dir(), help(myMath3) can generate documentation of the module myMath3 automatically.
  5. If you are only interested in a special function, you may also help(myMath3.add).

Namespace

  1. Namespace and scope differ in that a namespace maps names to objects, while a scope is the region of code where you can access a name. Namespaces allow Python to support the concept of scope, prevent naming conflicts, and enforce modularity.
  2. A Python program can have up to four different types of namespaces:
    1. Built-In
    2. Global
    3. Enclosing (Nonlocal)
    4. Local
  3. These namespaces have differing lifetimes. As Python executes a program, it creates namespaces as necessary and removes them when it no longer needs them.
  4. The Python global, local, and nonlocal namespaces are implemented as dictionaries. In contrast, the built-in namespace isn't a dictionary but a module called "builtins".
  5. You can list the objects in the built-in namespace with the dir() function:
  6. The Global Namespace: Python creates a main global namespace when the main program's body starts. This namespace remains in existence until the interpreter terminates.
  7. The Local Namespace: The Python interpreter creates a new and dedicated namespace whenever you call a function. This namespace is local to the function and destroyed after the function returns.
  8. When you are referencing a variable x from inside an inner function, Python looks for x in the following order:
    1. Local
    2. Enclosing
    3. Global
    4. Built-In
  9. Assignments in a function will create a local variable:
    x = 20
    def f():
        x = 40      # a local 'x' is created
        print(x)
    
    f()
    print(x)        # still 20 
  10. The global statement allows your function to modify a global variable:
    x = 20
    def f():
        global x
        x = 40
        print(x)
    
    f()
    print(x)        # Now it's 40
  11. You may produce the same result by
  12. You can specify several comma-separated names in a single global statement:

Reload a Module

  1. After a module is imported successfully (without syntax errors), if you modify the file and try to "import" again, the new contents will NOT be imported.
  2. This design is to improve the performance so that a module is not imported multiple time. (It can also prevent circular import issues.)
  3. The mechanism under the hood is that, Python stores the imported module in a dict sys.modules (the module cache). On subsequent imports, Python checks if the module is already in sys.modules and simply returns the cached version without re-executing the file.
  4. Python assumes modules don't change during execution, which is normally true in production.
  5. If you have modified the module source code and truly want it to be imported, you may delete it from the cache sys.modules:
  6. Alternatively, you may do it with a single method:

inspect.getsource

  1. import inspect
  2. inspect.getfile(object) - Returns the file path where an object is defined.
  3. inspect.signature(function)
  4. inspect.getfullargspec(function)
  5. print(inspect.getsource(myMath3.add))

Module Search Path

  1. When a module named my_module is imported, the interpreter first searches for a built-in module with that name. These module names are listed in sys.builtin_module_names.
  2. If not found, it then searches for a file named my_module.py in a list of directories given by the variable sys.path.
  3. sys.path is initialized from these locations: