6. Modules

 

 

 

 

 

Why use modules?

 

 

♦     Code reuse

♦     System name-space partitioning

♦     Implementing shared services or data

 

 

 

 

Module topics

 

♦     The basics

♦     Import variations

♦     Reloading modules

♦     Design concepts

♦     Modules are objects

♦     Package imports

♦     Odds and ends

♦     Module gotchas

 

 


 

Module basics

 

 

♦     Creating modules:        Python files, C extensions; Java classes (Jython)

♦     Using modules:            import, from, reload(), 3.X: imp.reload()

♦     Module search path:     $PYTHONPATH

 

 

 

 

file: module1.py

 

def printer(x):           # module attribute

    print x

 

 

 

Module usage

 

% python

>>> import module1                    # get module

>>> module1.printer('Hello world!')

Hello world!

 

>>> from module1 import printer       # get an export

>>> printer('Hello world!')

 

>>> from module1 import *             # get all exports

>>> printer('Hello world!')

 

 

 

 


 

 

“from *” can obscure variables’ meaning

 

>>> from module1 import *     # may overwrite my names

>>> from module2 import *     # no way to tell what we get

>>> from module3 import *

>>> . . .

>>> func()     # ← ?!

 

# Advice: use “from *” with at most 1 module per file

 

 

 

 

 

 

“from” does not play well with “reload”

 

>>> from module1 import func       # copy variable out

>>> func()                         # test it

 

…change module1.py…

 

>>> from imp import reload         # required in 3.X

>>> reload(module1)                # ← FAILS: unbound name!

 

>>> import module1                 # must bind name here

>>> reload(module1)                # ok: loads new code

>>> func()                         # ← FAILS: old object!

 

>>> module1.func()                 # this works now

 

>>> from module1 import func       # so does this

>>> func()

 

# Advice: don’t do that--run scripts other ways

 

 


 

 

Module files are a namespace

 

 

♦     A single scope: local==global

♦     Module statements run on first import

♦     Top-level assignments create module attributes

♦     Module namespace: attribute ‘__dict__’, or dir()

 

 

 

file: module2.py

print 'starting to load...'

 

import sys

name = 42

 

def func(): pass

 

class klass: pass

 

print 'done loading.'

 

 

Usage

>>> import module2

starting to load...

done loading.

 

>>> module2.sys

<module 'sys'>

>>> module2.name

42

>>> module2.func, module2.klass

(<function func at 765f20>, <class klass at 76df60>)

 

>>> module2.__dict__.keys()   # add list() in 3.X

['__file__', 'name', '__name__', 'sys', '__doc__', '__builtins__', 'klass', 'func']


 

 

 

 

Name qualification

 

 

♦     Simple variables

●      ‘X’ searches for name ‘X’ in current scopes

♦     Qualification

●      ‘X.Y’ searches for attribute ‘Y’ in object ‘X’

♦     Paths

●      ‘X.Y.Z’ gives a path of objects to be searched

♦     Generality

●      Qualification works on all objects with attributes: modules, classes, built-in types, etc.

 

 

 

 

 

 

 

 


 

Import variants

 

 

 

♦     Module import model

●      module loaded and run on first import or from

●      running a module’s code creates its top-level names

●      later import/from fetches already-loaded module

 

 

♦     import and from are assignments

●      import assigns an entire module object to a name

●      from assigns selected module attributes to names

 

 

 

 

Operation

Interpretation

import mod

fetch a module as a whole

from mod import name

fetch a specific name from a module

from mod import *

fetch all top-level names from a module

imp.reload(mod)

force a reload of module’s code

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Reloading modules

 

 

♦     Imports only load/run module code first time

♦     Later imports use already-loaded module

♦     reload function forces module code reload/rerun

♦     Allows programs to be changed without stopping

♦     3.X: must first “from imp import reload” to use!

 

 

General form

 

import module                # initial import

[use module.attributes]

...                          # change module file

...

from imp import reload       # required in 3.X

reload(module)               # get updated exports

[use module.attributes]

 

 

 

Usage details

 

♦     A function, not a statement

♦     Requires a module object, not a name

♦     Changes a module object in-place:

●      runs module file’s new code in current namespace

●      assignments replace top-level names with new values

●      impacts all clients that use ‘import’ to fetch module

●      impacts future ‘from’ clients (see earlier example)

 


 

Reload example (optional section)

 

♦     Changes and reload file without stopping Python

♦     Other common uses: GUI callbacks, embedded code, etc.

 

 

% cat changer.py

message = "First version"

 

def printer():

    print message

 

 

% python

>>> import changer

>>> changer.printer()

First version

>>> 

 

 

[modify changer.py without stopping python]

 

% vi changer.py

% cat changer.py

message = "After editing"

 

def printer():

    print 'reloaded:', message

 

 

[back to the python interpreter/program]

 

>>> import changer

>>> changer.printer()      # no effect: uses loaded module

First version

 

>>> reload(changer)        # forces new code to load/run

<module 'changer'>

>>> changer.printer()

reloaded: After editing

 

 

 


 

 

Package imports

 

Module package imports name directory paths:

♦      Module name → “dir.dir.dir…” in import statements and reloads

●      “import dir1.dir2.mod”    →   loads dir1\dir2\mod.py

●      “from dir1.dir2.mod import name”

♦      dir1 must be contained by a directory on sys.path (‘.’, PYTHONPATH, etc.)

♦      Each dir must have “__init__.py”  file, possibly empty (till 3.3: optional)

♦      __init__.py gives directory’s namespace, can use __all__ for “from*”

♦      Simplifies path, disambiguates same-named modules files

 

Example

 

For:

dir0\dir1\dir2\mod.py

And:

import dir1.dir2.mod

 

●      dir0 (container) must be listed on the module search path

●      dir1 and dir2 both must contain an __init__.py file

●      dir0 does not require an __init__.py

 

dir0\

    dir1\

        __init__.py

        dir2\

            __init__.py

            mod.py

 

 

 

Why packages?

 

root\

    sys1\

        __init__.py       (__init__ needed if dir in import)

        util.py

        main.py           (“import util” finds here)

        other.py

    sys2\

        __init__.py

        util.py

        main.py

        other.py

    sys3\                 (here or elsewhere)

        __init__.py       (your new code here)

        myfile.py         (“import util” depends on path)

                          (“import sys1.util” doesn’t)

 

 

 

Advanced: Relative import syntax (2.5+)

 

To enable in 2.X (standard in 3.X):

 

    from __future__ import absolute_import   # till 2.7, “from” stmt only

 

In code located in package folder pkg:

 

    import string    # skips pkg: finds the standard library's version

 

    from .string import name1, name2   # import names from pkg.string only

    from . import string               # import pkg.string

 

 

 

Advanced: Namespace packages (3.3+)

 

 

Extension to usual import algorithm: directories without __init__.py, located anywhere on path, checked for last, and used only if no normal module or package found at level: concatenation of all found becomes a virtual package for deeper imports.  Not yet used much in practice.

 

See Learning Python 5th Edition for more advanced package details.

 

 

 

 

Odds and ends

 

♦     Python 2.0+: “import module as name”

●      Like “import module” + “name = module”

●      Also good for packages: “import sys1.util as util”

 

♦     Loading modules by name string

●      exec(‘import ‘ + name)

●      __import__(name)

 

♦     Modules are compiled to byte code on first import

●      ‘.pyc’ files serve as recompile dependency

●      compilation is automatic and hidden

 

♦     Data hiding is a convention

●      Exports all names defined at the top-level of a module

●      Special case: __all__ list gives names exported by “from *”

●      Special case: “_X” names aren’t imported by a “from*”

 

♦     The __name__ == ‘__main__’ trick

●      __name__ auto set to ‘__main__’ only when run as script

●      allows modules to be imported and/or run

●      Simplest unit test protocol, dual usage modes for code

 

file: runme.py

 

def tester():

    print "It's Christmas in Heaven"

 

if __name__ == '__main__':       # only when run

    tester()                     # not when imported

 

Usage modes

 

% python

>>> import runme

>>> runme.tester()

It's Christmas in Heaven

 

% python runme.py

It's Christmas in Heaven


 

 

 

 

Module design concepts

 

 

♦     Always in a module: interactive = module __main__

♦     Minimize module coupling: global variables

♦     Maximize module cohesion: unified purpose

♦     Modules should rarely change other module’s variables

 

mod.py

X = 99                  # reader sees only this

 

import mod

print mod.X ** 2        # always okay to use

 

import mod

mod.X = 88              # almost always a Bad Idea!

 

import mod

result = mod.func(88)   # better: isolates coupling

 

 

 

 

 

 

 

 


 

 

Modules are objects: metaprograms

 

 

♦     A module which lists namespaces of other modules

♦     Special attributes: module.__name__, __file__, __dict__

♦     getattr(object, name) fetches attributes by string name

♦     Add to $PYTHONSTARTUP to preload automatically

 

 

 

File mydir.py

verbose = 1

 

def listing(module):

    if verbose:

        print "-"*30

        print ("name: %s file: %s" %

                 (module.__name__, module.__file__))

        print "-"*30

 

    count = 0

    for attr in module.__dict__.keys():      # scan names

        print "%02d) %s" % (count, attr),

        if attr[0:2] == "__":

            print "<built-in name>"          # skip specials

        else:

            print getattr(module, attr)      #__dict__[attr]

        count = count+1

 

    if verbose:

        print "-"*30

        print module.__name__, "has %d names" % count

        print "-"*30

 

if __name__ == "__main__":

    import mydir

    listing(mydir)      # self-test code: list myself

 


 

 

♦     Running the module on itself

 

 

 

C:\python> python mydir.py

------------------------------

name: mydir file: mydir.py

------------------------------

00) __file__ <built-in name>

01) __name__ <built-in name>

02) listing <function listing at 885450>

03) __doc__ <built-in name>

04) __builtins__ <built-in name>

05) verbose 1

------------------------------

mydir has 6 names

------------------------------

 

 

 


 

Another program about programs

 

 

♦     ‘exec’ runs strings of Python code

♦     ‘os.system’ runs a system shell command

♦     __dict__ attribute is module namespace dictionary

♦     ‘sys.modules’ is the loaded-module dictionary

 

 

 

file: fixer.py

editor = 'vi'  # your editor's name

 

def python(cmd):

    import __main__

    namespace = __main__.__dict__

    exec cmd in namespace, namespace  

    # 3.X: exec(cmd, namespace, namespace)

 

def edit(filename):

    import os

    os.system(editor + ' ' + filename)

 

def fix(modname):

    import sys                      # edit,(re)load

    edit(modname + '.py')

    if modname in sys.modules.keys():

        python('reload(' + modname + ')')

    else:

        python('import ' + modname)

 

 

 

% python

>>> from fixer import fix

>>> fix("spam")             # browse/edit, import by name

 

>>> spam.function()         # spam was imported in __main__

>>> fix("spam")             # edit and reload() by name

 

>>> spam.function()         # test new version of function


 

 

 

 

Module gotchas

 

 

 

“from” copies names but doesn’t link

 

nested1.py

X = 99

def printer(): print X

 

 

nested2.py

from nested1 import X, printer    # copy names out

X = 88                            # changes my "X" only!

printer()

 

 

nested3.py

import nested1             # get module as a whole

nested1.X = 88             # change nested1's X

nested1.printer()

 

 

% python nested2.py

99

% python nested3.py

88

 

 

 

Statement order matters at top-level

 

♦     Solution: put most immediate code at bottom of file

file: order1.py

func1()               # error: "func1" not yet assigned

 

def func1():

    print func2()     # okay:  "func2" looked up later

 

func1()               # error: "func2" not yet assihned

 

def func2():

    return "Hello"

 

func1()               # okay:  "func1" and "func2" assigned


 

Recursive “from” import gotchas

 

♦     Solution: use “import”, or “from” inside functions

file: recur1.py

X = 1

import recur2           # run recur2 now if doesn't exist

Y = 2

 

file recur2.py

from recur1 import X    # okay: "X" already assigned

from recur1 import Y    # error: "Y" not yet assigned

 

 

>>> import recur1

Traceback (innermost last):

  File "<stdin>", line 1, in ?

  File "recur1.py", line 2, in ?

    import recur2

  File "recur2.py", line 2, in ?

    from recur1 import Y    # error: "Y" not yet assigned

ImportError: cannot import name Y

 

 

 

 

“reload” may not impact “from” imports

 

♦     “reload” overwrites existing module object

♦     But “from” names have no link back to module

♦     Use “import” to make reloads more effective

import module        Þ module.X reflects module reloads

from module import X Þ X may not reflect module reloads!

 

 

 

“reload” isn’t applied transitively

 

♦     Use multiple “reloads” to update subcomponents

♦     Or use recursion to traverse import dependencies

 

See CD’s Extras\Code\Misc\reloadall.py


 

 

 

 

Optional reading: a shared stack module

 

 

♦     Manages a local stack, initialized on first import

♦     All importers share the same stack: single instance

♦     Stack accessed through exported functions

♦     Stack can hold any kind of object (heterogeneous)

 

 

file: stack1.py

 

stack = []                           # on first import

error = 'stack1.error'               # local exceptions

 

def push(obj):

    global stack                     # 'global' to change

    stack = [obj] + stack            # add item to front

 

def pop():    

    global stack

    if not stack:

        raise error, 'stack underflow'  # raise local error

    top, stack = stack[0], stack[1:]    # remove front item

    return top

 

def top():

    if not stack:                       # raise local error

        raise error, 'stack underflow'  # or let IndexError

    return stack[0]

 

def empty():      return not stack      # is the stack []?

def member(obj):  return obj in stack   # item in stack?

def item(offset): return stack[offset]  # index the stack

def length():     return len(stack)     # number entries

def dump():       print '<Stack:%s>' % stack

 

 

 


 

Using the stack module

 

% python

>>> import stack1

>>> for i in range(5): stack1.push(i)

...

>>> stack1.dump()

<Stack:[4, 3, 2, 1, 0]>

 

 

Sequence-like tools

>>> stack1.item(0), stack1.item(-1), stack1.length()

(4, 0, 5)

>>> stack1.pop(), stack1.top()

(4, 3)

>>> stack1.member(4), stack1.member(3)

(0, 1)

>>> for i in range(stack1.length()): print stack1.item(i),

...

3 2 1 0

 

 

Exceptions

>>> while not stack1.empty(): x = stack1.pop(),

...

>>> try:

...     stack1.pop()

... except stack1.error, message:

...     print message

...

stack underflow

 

 

Module clients

file: client1.py

from stack1 import *

push(123)                  # module-name not needed

result = pop()

 

file: client2.py

import stack1

if not stack1.empty():     # qualify by module name

    x = stack1.pop()

stack1.push(1.23)          # both clients share same stack


 

 

 

Lab Session 5

 

Click here to go to lab exercises

Click here to go to exercise solutions

Click here to go to solution source files

Click here to go to lecture example files