Dan_Patterson

Documenting function <||> function Documenting

Blog Post created by Dan_Patterson Champion on Nov 7, 2016

This is a sample output for a function (def) in python.  You will notice most of the code is format 'fluff'.  Why do it?  Because if you need to document something for posterity or a contract or a course, then you had better have as much information as possible..

 

So... the following is for those that have a need for documentation.  There is numerous examples of format tips therein as well.  I have also documented the function that does the documentation .... get_func ... .  I have another one called ... get_modu ... that documents whole modules.

 

Enjoy

:num_54() Producing uniformly distributed data
    :Requires:
    :--------
    :  The class numbers have to be specified and the number of repeats
    :  to give you a total population size.
    :Reference:
    :---------
    :  https://geonet.esri.com/thread/185566-creating-defined-lists
   
:Generate Data that conform to a uniform distribution.
:
:Class values: [1 2 3 4 5 6]
:Population size: 60
:Results:
:  values:
    [[3 5 2 3 4 2 4 4 1 2 5 6 3 4 1 5 1 5 2 3]
     [5 2 6 6 6 2 4 4 6 4 3 2 3 4 1 6 6 5 2 1]
     [3 6 1 3 1 6 4 2 4 1 1 6 5 5 5 2 3 3 1 5]]
:  table:
    [(0, 3) (1, 5) (2, 2) (3, 3) (4, 4) (5, 2) (6, 4) (7, 4) (8, 1) (9, 2) (10, 5)
     (11, 6) (12, 3) (13, 4) (14, 1) (15, 5) (16, 1) (17, 5) (18, 2) (19, 3) (20, 5)
     (21, 2) (22, 6) (23, 6) (24, 6) (25, 2) (26, 4) (27, 4) (28, 6) (29, 4) (30, 3)
     (31, 2) (32, 3) (33, 4) (34, 1) (35, 6) (36, 6) (37, 5) (38, 2) (39, 1) (40, 3)
     (41, 6) (42, 1) (43, 3) (44, 1) (45, 6) (46, 4) (47, 2) (48, 4) (49, 1) (50, 1)
     (51, 6) (52, 5) (53, 5) (54, 5) (55, 2) (56, 3) (57, 3) (58, 1) (59, 5)]
:  histogram: (class, frequency)
    [[ 1 10]
     [ 2 10]
     [ 3 10]
     [ 4 10]
     [ 5 10]
     [ 6 10]]
:Then use NumPyArrayToTable to get your table.

>>> print(art.get_func(num_54))

:-----------------------------------------------------------------
:Function: .... num_54 ....
:Line number... 664
:Docs:
num_54() Producing uniformly distributed data
    :Requires:
    :--------
    :  The class numbers have to be specified and the number of repeats
    :  to give you a total population size.
    :Reference:
    :---------
    :  https://geonet.esri.com/thread/185566-creating-defined-lists
   
:Defaults: None
:Keyword Defaults: None
:Variable names: ('frmt', 'st', 'end', 'vals', 'reps', 'z', 'ID', 'tbl', 'h', 'pad', 'args')
:Source code:
   0  def num_54():
   1      """num_54() Producing uniformly distributed data
   2      :Requires:
   3      :--------
   4      :  The class numbers have to be specified and the number of repeats
   5      :  to give you a total population size.
   6      :Reference:
   7      :---------
   8      :  https://community.esri.com/thread/185566-creating-defined-lists
   9      """

  10      frmt = """
  11      :{}
  12      :Generate Data that conform to a uniform distribution.
  13      :
  14      :Class values: {}
  15      :Population size: {}
  16      :Results:
  17      :  values:
  18      {}
  19      :  table:
  20      {}
  21      :  histogram: (class, frequency)
  22      {}
  23      :Then use NumPyArrayToTable to get your table.
  24      """

  25      # import numpy as np
  26      st = 1
  27      end = 7
  28      vals = np.arange(st,end)
  29      reps = 10
  30      z = np.repeat(vals,reps)
  31      np.random.shuffle(z)
  32      ID = np.arange(len(z))
  33      tbl = np.array(list(zip(ID, z)),
  34                     dtype = [('ID', 'int'), ('Class', 'int')])
  35      h = np.histogram(z, np.arange(st, end+1))
  36      h = np.array(list(zip(h[1], h[0])))
  37      pad = "    "
  38      args =[num_54.__doc__, vals, reps*len(vals),
  39             indent(str(z.reshape(3,20)), pad),
  40             indent(str(tbl), pad), indent(str(h), pad)]
  41      print(dedent(frmt).format(*args))

:
:-----------------------------------------------------------------

 

Now the function that documents the function documenting itself.

 

>>> print(art.get_func(art.get_func))

:-----------------------------------------------------------------
:Function: .... get_func ....
:Line number... 485
:Docs:
Get function (def) information.
    :Requires: 
    :--------
    :  from textwrap import dedent, indent, wrap
    :  import inspect
    :Returns:
    :-------
    :  The function information includes arguments and source code.
    :  A string is returned for printing.

:Defaults: (True,)
:Keyword Defaults: None
:Variable names:
    obj, verbose, frmt, inspect, dedent, indent, wrap,
    lines, ln_num, code, vars, args, code_mem
:Source code:
   0  def get_func(obj, verbose=True):
   1      """Get function (def) information.
   2      :Requires: 
   3      :--------
   4      :  from textwrap import dedent, indent, wrap
   5      :  import inspect
   6      :Returns:
   7      :-------
   8      :  The function information includes arguments and source code.
   9      :  A string is returned for printing.
  10      """

  11      frmt = """
  12      :-----------------------------------------------------------------
  13      :Function: .... {} ....
  14      :Line number... {}
  15      :Docs:
  16      {}
  17      :Defaults: {}
  18      :Keyword Defaults: {}
  19      :Variable names:
  20      {}
  21      :Source code:
  22      {}
  23      :
  24      :-----------------------------------------------------------------
  25      """

  26      import inspect
  27      from textwrap import dedent, indent, wrap
  28      lines, ln_num = inspect.getsourcelines(obj)
  29      code = "".join(["{:4d}  {}".format(idx, line)
  30                      for idx, line in enumerate(lines)])
  31      vars  = ", ".join([i for i in obj.__code__.co_varnames])
  32      vars = wrap(vars, 50)
  33      vars = "\n".join([i for i in vars])
  34      args = [obj.__name__, ln_num, dedent(obj.__doc__), obj.__defaults__,
  35               obj.__kwdefaults__,indent(vars, "    "), code]       
  36      code_mem = dedent(frmt).format(*args)
  37      return code_mem

:
:-----------------------------------------------------------------

 

The two modules that do the heavy lifting are the inspect and textwrap modules.  You can obtain more information on these by simply using the 'dir' and 'help' functions (ie. dir(textwrap) ) to get details of the methods used.  Textwrap allows for indentation, dendentation and wrapping of text blocks.  Pretty well anything can be passed to the builtins if they can be converted to string format.  You will notice that I make extensive use of 'dedent' because def headers are indented by 4 spaces, which I would like to remove prior to printing.

 

Learn something about formatting and worry less about reducing code length... it is not about the bytes saved when coding ... it is about code coming back to bite you when you can't remember how it works.

 

As a parting easy one, this is a decorator which you can use in your own scripts.  Edit to suit your return needs

 

def func_run(func):
    """Prints basic function information and the results of a run.
    :Required:  from functools import wraps
    :  Uncomment the import or move it to within the script.
    :Useage:   @func_run  on the line above the function
    """

    from functools import wraps
    @wraps(func)
    def wrapper(*args,**kwargs):
        frmt = "\n".join(["Function... {}", "  args.... {}",
                          "  kwargs.. {}", "  docs.... {}"])
        ar = [func.__name__, args, kwargs, func.__doc__]
        print(dedent(frmt).format(*ar))
        result = func(*args, **kwargs)
        print("{!r:}\n".format(result))  # comment out if results not needed
        return result                    # for optional use outside.
    return wrapper

 

Enjoy

Outcomes