Tuesday, July 10, 2018

Python Modules

Introduction
------------

- collection of functions placed in separate files
- it may also contain executable statements
- filename convention: .py
- a module can be imported in any python script or in another module
- importing a module: `import `
- module name is stored in `__name__`
- module locations:
    * linux/unix -> /usr/lib*/python*/
    * windows    -> \Lib
- a module contains its own private symbol table
    * used as global symbol table of all functions defined inside the module
    * to modify a module's global variable: `mod_name.itame_name = value`
- each module is only imported once per interpreter session

Using modules
-------------

Listing all available modules
>>> help('modules')
Importing a module
# sample module content of fibo.py
def fib(n):    # write Fibonacci series up to n
    a, b = 0, 1
    while b < n:
        print(b, end=' ')
        a, b = b, a+b
    print()

def fib2(n): # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while b < n:
        result.append(b)
        a, b = b, a+b
    return result

# Importing this way will import the module name but not the name of
# function definitions inside. You need to explicitly call the names
# of functions to use it.
>>> import fibo
>> fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(1000)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
>>>

# Another way of importing modules is to just import the specific
# function definitions inside it. By doing it, it will not import the
# module name but it will import the function names so calling the
# functions is like as if the functions are locally defined.
>>> from fibo import fib, fib2
>>> fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fib2(1000)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
>>>

# This method will import all functions name except those beginning
# with `_`. This is not a good practice because this might override
# the local functions you have. This maybe appropriately used in
# interactive sessions to save you from typing.
>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
>>>
Accessing functions inside a module
>>> fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(1000)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
>>>
You may also assign a local name for the
module function
>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
Peeking at the module name
>>> fibo.__name__
'fibo'
>>>
Restarting the interpreter - useful if
you change your modules
>>> import importlib
>>> importlib.reload(module_name)

Running modules as a script
---------------------------

To run a module as a script, we need to add the following code inside:

if __name__ == '__main__':
  do something here..

Why do we need it?

When a python program runs, the interpreter sets few variables at start and one
of them is `__name__` whose value is set to `__main__`.

# code
print(__name__)

# output
__main__

When a module is imported, the interpreter sets `__name__` to the module's
filename.

# module -- filename: sample_module.py
print(__name__)

# main program
import sample_module

# output of running main program
sample_module

So in order to run a module as a script, we need to tell the interpreter what to
do when `__name__` equals `__main__`

if __name__ == '__main__':
  print('I am being run as a script')
else:
  print('I am imported as a module')

Module Paths
------------

When a python imports a module, it first searches the built-in modules
for the module name then the paths defined by "sys.path".

"sys.path" contains the following folders/directories:
  - directory containing the script being run
  - current directory (if no script is run)
  - PYTHONPATH
  - installation directories
>>> import sys
>>>
>>> sys.path
['', 'C:\\Users\\john\\AppData\\Local\\Programs\\Python\\Python36\\python36.zip', 'C:\\Users\\john\\AppData\\Local\\Programs\\Python\\Python36\\DLLs', 'C:\\Users\\john\\AppData\\Local\\Programs\\Python\\Python36\\lib', 'C:\\Users\\john\\AppData\\Local\\Programs\\Python\\Python36', 'C:\\Users\\john\\AppData\\Local\\Programs\\Python\\Python36\\lib\\site-packages']
>>>
If you run a script, python adds to `sys.path` the location of the
script being run
Create file C:\Users\john\Python Scripts\sample.py
import sys
for i in sys.path:
  print(i)

Try running it
C:\Users\john
λ python "C:\Users\john\Python Scripts\sample.py"
C:\Users\john\Python Scripts
C:\Users\john\AppData\Local\Programs\Python\Python36\python36.zip
C:\Users\john\AppData\Local\Programs\Python\Python36\DLLs
C:\Users\john\AppData\Local\Programs\Python\Python36\lib
C:\Users\john\AppData\Local\Programs\Python\Python36
C:\Users\john\AppData\Local\Programs\Python\Python36\lib\site-packages

Compiled Python Files
---------------------

- python caches compiled version of modules under `__pycache__` directory to
  speed loading of modules
- the compiled version is named using the following format: `module.version.pyc`
- a compiled module loads faster since it is cached but speed of execution is
  same as the non-compiled version
- instances where cache is not check/used:
    1. doesn't cache modules that are loaded directly from command line
    2. doesn't check cache if there is no source module
- python checks the modification date of the source file against the compiled
  version; if it is out of date then it is recompiled auomatically
- compiled modules are platform independent
- tips:
    * -0 or -00 command swithches reduces size of compiled modules
    * module `compilall` can create .pyc files for all modules in a directory
    * see PEP 3147 for detailed information regarding python compilation

Standard Modules
----------------

- to be discussed in detail on separate page
- some modules are built-in into the interpreter (e.g sys)
- there are modules that are platform dependent (e.g winreg for windows)

dir() function
--------------

dir() returns a sorted list of all names a module
defines

  * functions
  * variables
  * etc..
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_enablelegacywindowsfsencoding', '_getframe', '_git', '_home', '_xoptions', 'api_version', 'argv', 'base_exec_prefix', 'base_prefix', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dllhandle', 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_wrapper', 'getallocatedblocks', 'getcheckinterval', 'getdefaultencoding', 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace', 'getwindowsversion', 'hash_info', 'hexversion', 'implementation', 'int_info', 'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'set_asyncgen_hooks', 'set_coroutine_wrapper', 'setcheckinterval', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', 'version', 'version_info', 'warnoptions', 'winver']
>>>
without any arguments, it will return names you defined
>>> dir()
['__annotations__', '__builtins__', '__cached__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'os', 'socket']
>>> a = 'apple'
>>> def test():
...   pass
...
>>> dir()
['__annotations__', '__builtins__', '__cached__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'os', 'socket', 'test']
>>>
dir() doesn't list names of built-in functions
and variables.

To get the list, use the module `builtins`
>>> import builtins
>>> dir(builtins) 
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError',
 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning',
 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
 'FileExistsError', 'FileNotFo
...

Packages
--------

- a collection of modules
- a directory containing subdirectories (for other modules) or modules
- enables importing modules using dotted format: `import module1.module2.module3`
- `__init__.py`
    * a file put in the directory to mark it as package directory
    * can be empty
    * can also contain initializatin code

How to use packages?

Having this sample structure,
sound/                          # top-level package
      __init__.py               # initialize the sound package
      formats/                  # subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  # subpackage for sound effects
              __init__.py       # initialize the effects package
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  # subpackage for filters
              __init__.py       # initialize the filters package
              equalizer.py
              vocoder.py
              karaoke.py
              ...
 
You can import modules in different ways:

METHOD 1 -- full name
import sound.effects.echo
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

METHOD 2 -- explicitly importing the module name
from sound.effects import echo
echo.echofilter(input, output, delay=0.7, atten=4)

METHOD 3 -- importing a specific function from the module
from sound.effects.echo import echofilter
echofilter(input, output, delay=0.7, atten=4)

METHOD 4 -- importing submodules from a package
from sound.effects import *
echo.echofilter(input='hi', output='there', delay=0.7, atten=4)

NOTES:
  - for method 1, last item in the import must be a package or module but not a
    function, class, or variable
  - method 4 is discussed in detail on the next section
Importing variables
You may also import variables from other module like this way:

from package.module import variable
Wild imports
Using `from moduleA.moduleB import *` will import all modules defined in `__all__` under a package's `__init__.py`
 
Having this definition in sound/effects/__init__.py
__all__ = ["echo", "surround", "reverse"]

.. will make echo, surround, and reverse available to use
echo.echofilter(input='hi', output='there', delay=0.7, atten=4)

A module(s) that has been imported from wild imports can still be imported explicitly
from sound.effects import *
import sound.effects.echo
import sound.effects.surround

using wild imports is not a good practice since it may import names that you don't need and may
mess up your code. It is better to explicitly import what you need by using
`from package import module`.
Intra-package references
Modules from a subpackage can import a module from a different subpackage using:

METHOD 1: using full name of module, e.g from `vocader` module
from sound.effects import echo

METHOD 2: using relative imports, e.g from `surround` module
from . import echo
from .. import formats
from ..filters import equalizer

No comments:

Post a Comment