Python code that has some divergences in regards to logger, printing statistics, writing of files, file paths and docstring

I think I have corrected most of the divergences but I am still concerned about docstring especially. For example, Docstrings should always be placed directly under the function specification, within its body. Have I used docstrings in an appropriate way or? If you still can find any errors regarding file paths, logger, printing statitics, writing of files, my comments and docstring, please let me know.

Code & Output

All file paths needs to be based upon variable RESOURCES and in order of providing platform independence all path’s should be constructed using pathlib. I need to change value of RESOURCES:

RESOURCES = Path(__file__).parent / '../_Resources/'

Logger

Name of logger needs to be ass_3_logger. Check

Printing Statistics

Duration headers need to be aligned with duration values, in a table column fashion, and name of Fibonacci approaches needs to be formatted in conformity to specified requirements:

---------------------------------------------------------------------------
              DURATION FOR EACH APPROACH WITHIN INTERVAL: 30-0              
---------------------------------------------------------------------------
                       Seconds   Milliseconds   Microseconds    Nanoseconds
Fib Iteration          0.00047        0.47289          472.9         472892
Fib Recursion          0.74339      743.39246       743392.5      743392458
Fib Memory             0.00052        0.52040          520.4         520404

I think I have corrected this divergence, not a 100% sure though.

Writing Files

Works as intended and mostly meets stated requirements. However, the name of produced files does not conform to stated requirements.

The implementation would perhaps become cleaner using zip() which lets us traverse two containers at the same time:

with open(file_path, 'w') as file:
    for idx, value in zip(range(len(details(1)) - 1, -1, -1), details(1)):
        file.write("{}: {}n".format(idx, value))

Complement

  • Attend divergence in regards to logger!
  • Attend divergences in regards to printing statistics!
  • Attend divergence in regards to writing of files!
  • Make sure all file paths are constructed properly using
    pathlib.Path().
  • I have to add more comments which describes my implementations, and make sure
    I also place docstrings within the body of respective function!

Complete solution:

#!/usr/bin/env python

""" LAB ASSIGNMENT 3
Below you find the inherent code, some of which fully defined. You add implementation
for those functions which are needed:

 - create_logger()
 - measurements_decorator(..)
 - fibonacci_memory(..)
 - print_statistics(..)
 - write_to_file(..)
"""

from pathlib import Path
from timeit import default_timer as timer
from functools import wraps
import argparse
import logging
import logging.config
import json
import codecs
import time

__version__ = '1.0'
__desc__ = "Program used for measuring execution time of various Fibonacci implementations!"

LINE = 'n' + ("---------------" * 5)
RESOURCES = Path.cwd() / "../_Resources/"
LOGGER = None  # declared at module level, will be defined from main()


def create_logger() -> logging.Logger:
    """Create and return logger object.
    Purpose: This method creates object for logger and return the object
    :param : None
    :return : Logger object."""

    logging.basicConfig()
    logger = logging.getLogger()

    # Load the configuration.
    config_file = str(RESOURCES) + "/ass3_log_conf.json"
    with codecs.open(config_file, "r", encoding="utf-8") as fd:
        config = json.load(fd)

    # Set up proper logging. This one disables the previously configured loggers.
    logging.config.dictConfig(config)
    logger = logging.getLogger('ass_3_logger')
    return logger


def measurements_decorator(func):
    """Function decorator, used for time measurements.
    Purpose: This is a decorator which is used for measurement
    of the functions execution and printing logs
    :param : func
    :return : tuple(float,dictionary)."""

    @wraps(func)
    def wrapper(nth_nmb: int) -> tuple:
        result = {}
        k = 5
        ts = time.time()
        LOGGER.info("Starting measurements...")
        for i in reversed(range(nth_nmb + 1)):
            result(i) = func(i)
            if k == 5:
                LOGGER.debug(str(i) + " : " + str(result(i)))
                k = 0
            k += 1
        te = time.time()
        return (te - ts), result

    return wrapper


@measurements_decorator
def fibonacci_iterative(nth_nmb: int) -> int:
    """An iterative approach to find Fibonacci sequence value.
    YOU MAY NOT MODIFY ANYTHING IN THIS FUNCTION!!  """

    """Purpose: This is function to calculate fibonacci series using iterative approach
    :param : int
    :return : int."""

    old, new = 0, 1
    if nth_nmb in (0, 1):
        return nth_nmb
    for __ in range(nth_nmb - 1):
        old, new = new, old + new
    return new


@measurements_decorator
def fibonacci_recursive(nth_nmb: int) -> int:
    """An recursive approach to find Fibonacci sequence value.
    YOU MAY NOT MODIFY ANYTHING IN THIS FUNCTION!!"""
    """Purpose: This is function to calculate fibonacci recursion using iterative approach 
    :param : int
    :return : int."""

    def fib(_n):
        return _n if _n <= 1 else fib(_n - 1) + fib(_n - 2)

    return fib(nth_nmb)


@measurements_decorator
def fibonacci_memory(nth_nmb: int) -> int:
    """An recursive approach to find Fibonacci sequence value, storing those already calculated.
    Purpose: This is function to calculate fibonacci series using memory approach
    :param : int
    :return : int."""

    memory_dict = {0: 0, 1: 1}

    def fib(_n):
        if _n not in memory_dict:
            memory_dict(_n) = fib(_n - 1) + fib(_n - 2)
        return memory_dict(_n)

    return fib(nth_nmb)


def duration_format(duration: float, precision: str) -> str:
    """Function to convert number into string. Switcher is dictionary type here.
        Purpose: This is a function to proper format for duration
        :param: float, str
        :return : str."""
    switcher = {
        'Seconds': "{:.5f}".format(duration),
        'Milliseconds': "{:.5f}".format(duration * 1_000),
        'Microseconds': "{:.1f}".format(duration * 1_000_000),
        'Nanoseconds': "{:d}".format(int(duration * 1_000_000_000))
    }

    # get() method of dictionary data type returns value of passed argument if it is present in
    # dictionary otherwise second argument will be assigned as default value of passed argument
    return switcher.get(precision, "nothing")


# purpose of this function is to display the statics
def print_statistics(fib_details: dict, nth_value: int):
    """Function which handles printing to console."""
    print(LINE)
    print("nt  DURATION FOR EACH APPROACH WITHIN INTERVAL: " + str(nth_value) + "-0")
    print(LINE)
    print("{0}tttt {1:<7}t {2:<7}t {3:<7}t {4:<7}".format("", "Seconds", "Milliseconds", "Microseconds",
                                                             "Nanoseconds"))
    for function in fib_details:
        print("{0}t {1:<7}t {2:<13}t {3:<14}t {4:<7}".format(function,
                                                               duration_format(fib_details(function)(0), "Seconds"),
                                                               duration_format(fib_details(function)(0),
                                                                               "Milliseconds"),
                                                               duration_format(fib_details(function)(0),
                                                                               "Microseconds"),
                                                               duration_format(fib_details(function)(0),
                                                                               "Nanoseconds")))


# purpose of this function is to write results into file.
def write_to_file(fib_details: dict):
    """Function to write information to file."""
    for function in fib_details:
        with open(str(RESOURCES) + "//" + function + ".txt", "w") as file:
            for idx, value in zip(range(len(fib_details(function)(1)) - 1, -1, -1), fib_details(function)(1).values()):
                file.write("{}: {}n".format(idx, value))


def main():
    """The main program execution. YOU MAY NOT MODIFY ANYTHING IN THIS FUNCTION!!"""
    epilog = "DT179G Assignment 3 v" + __version__
    parser = argparse.ArgumentParser(description=__desc__, epilog=epilog, add_help=True)
    parser.add_argument('nth', metavar='nth', type=int, nargs='?', default=30,
                        help="nth Fibonacci sequence to find.")

    global LOGGER  # ignore warnings raised from linters, such as PyLint!
    LOGGER = create_logger()

    args = parser.parse_args()
    nth_value = args.nth  # nth value to sequence. Will fallback on default value!

    fib_details = {  # store measurement information in a dictionary
        'fib iteration': fibonacci_iterative(nth_value),
        'fib recursion': fibonacci_recursive(nth_value),
        'fib memory   ': fibonacci_memory(nth_value)
    }

    print_statistics(fib_details, nth_value)  # print information in console
    write_to_file(fib_details)  # write data files


if __name__ == "__main__":
    main()