Legends ofPythos
Claim your name
The Standard Library

Logging, timing and statistics

Lesson 5 of 7

Watch the lesson1:31 · with Torsten
print() is fine for quick experiments. In a real program you want more control: messages carry a level, and one setting decides how much detail reaches the screen.

The logging module gives every message a severity tag:
  • DEBUG – development noise
  • INFO – normal milestones (user logged in, order placed)
  • WARNING – something unexpected but handled
  • ERROR – an operation failed
  • CRITICAL – the program cannot continue
logging.basicConfig() sets up the root logger, which every other logger passes its messages up to. The two arguments that matter here:

level=logging.INFO – messages below INFO (i.e. DEBUG) are silently dropped.
stream=sys.stdout – send the text to standard output so this course can read it.
import logging
import sys

logging.basicConfig(
    level=logging.INFO,
    format='%(levelname)s:%(name)s:%(message)s',
    stream=sys.stdout,
    force=True,
)
logger = logging.getLogger('shop')
logger.debug('building cart for user 42')   # hidden (DEBUG < INFO)
logger.info('order placed: item=book qty=1')
Basic setup and a named logger
Notice the output is one line:
INFO:shop:order placed: item=book qty=1
The DEBUG line never appears. If you later set level=logging.DEBUG, that same logger.debug(...) call prints, and the call itself never changes.

Quick statistics with statistics

import statistics

data = [2.5, 3.0, 4.5, 6.0]
print(statistics.mean(data))     # average
print(statistics.median(data))   # middle value (or midpoint of two middles)
print(statistics.mode([1, 1, 2]))# most frequent value
print(round(statistics.stdev([2, 4, 6]), 3))
Mean, median, mode and standard deviation

Measuring how long code takes

import time

t0 = time.perf_counter()
for i in range(1_000):
    pass
elapsed = time.perf_counter() - t0
print(f'{elapsed:.6f} seconds')
perf_counter for a single run
timeit.timeit runs a function many times and returns the total wall-clock seconds. It is handy for comparing two implementations without writing your own loop.
import timeit

def square(n):
    return n * n

total = timeit.timeit(lambda: square(12), number=1000)
print(f'{total:.6f} seconds for 1000 calls')
timeit over 1000 calls

Your turn

0 of 3 solved

Exercise 1

+40 XP
Configure logging with logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(name)s:%(message)s', stream=sys.stdout, force=True). Make a logger named 'shop', then log 'building cart' at DEBUG level, 'ready' at INFO level and 'low stock' at WARNING level. Only the last two should appear in the output.
import logging
import sys


# configure logging, get the 'shop' logger, then log the three messages

Run your code to check it against the tests.

Exercise 2

+40 XP
Write measure(func, *args), which calls func(*args), times the call with time.perf_counter() before and after, and returns a tuple of the result and the seconds it took. For example, measure(sum, [1, 2, 3]) returns (6, 0.0000...), where the time is some small float.
import time




def measure(func, *args):
    # perf_counter before and after, return (result, seconds)
    pass

Run your code to check it against the tests.

Exercise 3

+40 XP
Write describe(values), which returns a dictionary with the 'mean', 'median' and 'mode' of a list of numbers, from the statistics module, and the 'stdev' rounded to 2 decimal places. For example, describe([2, 4, 4, 5])['mode'] is 4.
import statistics




def describe(values):
    pass

Run your code to check it against the tests.