1
\$\begingroup\$

In Python you can use print(locals()) to print a dictionary of all the local variables, but this has 3 shortcomings:

  1. Double underscore variables will be included that is not what we usually want.

  2. The output is in black and white, that can make it harder to read.

  3. Only the variable names and their values are printed, there is no way to automatically print the result of applying a function to each variable if possible and printing the result.

It is possible to use a debugger, but sometimes having all the output in one place and being able to go back and forth can be more useful then stepping through a debugger.

As such I have written the print_locals functions to solve these problems:

def print_locals(locals_, funcs=[], label="", color=True):
    from collections import OrderedDict
    from pprint import pprint

    # https://stackoverflow.com/questions/60031216/how-to-change-the-key-color-when-printing-a-python-dictionary
    def dprint(d,key_format = "\033[1;32m",value_format = "\033[1;34m"):
        for key in d.keys() :
            print (f" {key_format}{key}: {value_format}{repr(d[key])[:500]}")
    locs = {key:value for key, value in locals_.items() if not key.startswith('__')}
    out = OrderedDict()
    def identity(x):
        return x
    for key, value in sorted(locs.items()):
        for func in [identity] + funcs:
            try:
                out[key+("_"+func.__name__ if func.__name__ != "identity" else "")] = func(value)
            except (TypeError, ValueError, AttributeError):
                pass
    print(("\n\033[1;33m" if color else "\n")+label)
    dprint(out, key_format = "\033[1;32m" if color else "", value_format = "\033[1;34m" if color else "")

And here is an example usage:

import numpy as np
from scipy import stats

def example():
    def first_item(x):
        return x[0]
    def describe_flattened(x):
        return stats.describe(x.flatten())
    x = [1,2,3,4,5]
    print_locals(locals(), [first_item, len, stats.describe, np.shape, int], "start")
    x += [7,9]
    name = "Caridorc"
    xs = np.array([[4,5,6],
                   [7,8,18],
                   [9,10,11],
                   [12,13,14],
                   [15,16,17]])
    ys = np.mean(xs, axis=0)
    xs_squared = xs**2
    big = np.random.rand(1000,1000)
    print_locals(locals(), [first_item, len, stats.describe, describe_flattened, np.shape, int], "end")
    
if __name__ == "__main__":
    example()

With output (in reality it is colorized but here color cannot be shown):

start
 describe_flattened: <function example.<locals>.describe_flattened at 0x7f19cb8dd5e0>
 describe_flattened_shape: ()
 first_item: <function example.<locals>.first_item at 0x7f19cb8dd550>
 first_item_shape: ()
 x: [1, 2, 3, 4, 5]
 x_first_item: 1
 x_len: 5
 x_describe: DescribeResult(nobs=5, minmax=(1, 5), mean=3.0, variance=2.5, skewness=0.0, kurtosis=-1.3)
 x_shape: (5,)

end
 big: array([[0.98288814, 0.97630647, 0.26132766, ..., 0.58954191, 0.25681903,
        0.33411866],
       [0.50299533, 0.65961947, 0.73978812, ..., 0.29573779, 0.23369551,
        0.97092339],
       [0.90263252, 0.7760721 , 0.19466284, ..., 0.51350822, 0.52413125,
        0.65852317],
       ...,
       [0.7144501 , 0.99647271, 0.82473009, ..., 0.31224817, 0.13596358,
        0.47216351],
       [0.92383261, 0.42948829, 0.89493352, ..., 0.21755526, 0.18629585,
        0.7345645 ],
       [0.09757243
 big_first_item: array([9.82888140e-01, 9.76306470e-01, 2.61327662e-01, 8.91590583e-02,
       9.55163546e-01, 9.08456958e-01, 7.53214382e-01, 6.82397143e-01,
       4.95854979e-01, 2.87399085e-01, 6.63777609e-01, 4.19908536e-01,
       4.11669375e-01, 9.92319543e-01, 2.26913289e-01, 6.02279465e-01,
       4.05968774e-01, 7.71524127e-01, 2.14888903e-02, 1.83519681e-01,
       4.59032104e-01, 6.65706325e-01, 3.71472360e-01, 1.23070128e-01,
       5.79076106e-01, 1.52091773e-01, 6.34641048e-01, 1.34286564e-01,
   
 big_len: 1000
 big_describe: DescribeResult(nobs=1000, minmax=(array([3.11409704e-04, 6.81647421e-04, 1.31582825e-04, 1.56104348e-03,
       2.84670274e-03, 6.06042558e-04, 1.66119570e-03, 3.11966810e-04,
       9.29703789e-04, 1.06716128e-03, 4.87965396e-04, 1.20570178e-03,
       1.05329790e-03, 4.74552749e-04, 3.46799356e-04, 2.40614186e-04,
       1.46001935e-03, 2.71866983e-04, 6.58164425e-04, 1.24498327e-03,
       2.03076845e-04, 4.57861245e-03, 3.47527704e-03, 1.80359818e-04,
       2.54012443e-03, 6.37231150e-04, 3
 big_describe_flattened: DescribeResult(nobs=1000000, minmax=(4.2246054110517406e-07, 0.9999996319801028), mean=0.5001587336155897, variance=0.08331188852436676, skewness=0.0004189518459810173, kurtosis=-1.200281952258119)
 big_shape: (1000, 1000)
 describe_flattened: <function example.<locals>.describe_flattened at 0x7f19cb8dd5e0>
 describe_flattened_shape: ()
 first_item: <function example.<locals>.first_item at 0x7f19cb8dd550>
 first_item_shape: ()
 name: 'Caridorc'
 name_first_item: 'C'
 name_len: 8
 name_shape: ()
 x: [1, 2, 3, 4, 5, 7, 9]
 x_first_item: 1
 x_len: 7
 x_describe: DescribeResult(nobs=7, minmax=(1, 9), mean=4.428571428571429, variance=7.952380952380953, skewness=0.4423276254324468, kurtosis=-0.9777152282261832)
 x_shape: (7,)
 xs: array([[ 4,  5,  6],
       [ 7,  8, 18],
       [ 9, 10, 11],
       [12, 13, 14],
       [15, 16, 17]])
 xs_first_item: array([4, 5, 6])
 xs_len: 5
 xs_describe: DescribeResult(nobs=5, minmax=(array([4, 5, 6]), array([15, 16, 18])), mean=array([ 9.4, 10.4, 13.2]), variance=array([18.3, 18.3, 23.7]), skewness=array([ 0.07797781,  0.07797781, -0.52792179]), kurtosis=array([-1.21521992, -1.21521992, -1.08024889]))
 xs_describe_flattened: DescribeResult(nobs=15, minmax=(4, 18), mean=11.0, variance=20.0, skewness=0.0, kurtosis=-1.210714285714286)
 xs_shape: (5, 3)
 xs_squared: array([[ 16,  25,  36],
       [ 49,  64, 324],
       [ 81, 100, 121],
       [144, 169, 196],
       [225, 256, 289]])
 xs_squared_first_item: array([16, 25, 36])
 xs_squared_len: 5
 xs_squared_describe: DescribeResult(nobs=5, minmax=(array([16, 25, 36]), array([225, 256, 324])), mean=array([103. , 122.8, 193.2]), variance=array([ 6883.5,  8354.7, 14054.7]), skewness=array([ 0.51788093,  0.47995029, -0.19190912]), kurtosis=array([-1.08482927, -1.10598554, -1.39461119]))
 xs_squared_describe_flattened: DescribeResult(nobs=15, minmax=(16, 324), mean=139.66666666666666, variance=9974.666666666668, skewness=0.4476084348250222, kurtosis=-1.0493737094260638)
 xs_squared_shape: (5, 3)
 ys: array([ 9.4, 10.4, 13.2])
 ys_first_item: 9.4
 ys_len: 3
 ys_describe: DescribeResult(nobs=3, minmax=(9.4, 13.2), mean=11.0, variance=3.8799999999999977, skewness=0.5076720034433699, kurtosis=-1.4999999999999998)
 ys_describe_flattened: DescribeResult(nobs=3, minmax=(9.4, 13.2), mean=11.0, variance=3.8799999999999977, skewness=0.5076720034433699, kurtosis=-1.4999999999999998)
 ys_shape: (3,)

How can I improve both the quality of my code and make the output easier to understand? (Some imports are inside the print_locals function to make it easier to use just by copy pasting the function where needed).

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

This is a lovely function. LGTM, ship it!

Running it through black would make it PEP8 compliant.

The octal escapes are perfectly clear. But it wouldn’t hurt to define the occasional symbolic constant that explains the effect of sending an escape sequence.

Defining the identity function is a nice touch.

The pprint module offers both pprint and pp, which is sometimes more convenient. Occasionally, I will also serialize a data structure using the JSON module. Each of these has its strength, it is not clear how to choose among them in the general case. So what you have here looks perfectly nice. More usage of it will inform the direction to head in.

\$\endgroup\$
2
  • \$\begingroup\$ I just noticed that pretty print was used in an old version but not anymore, so the import can just be removed. Also yeah I was very indecisive between returning the dictionary from the function and printing it, maybe I should add an argument to decide between printing and returning with printing being the default for simplicity. Thanks for the insight :) \$\endgroup\$
    – Caridorc
    Feb 6, 2023 at 19:22
  • 1
    \$\begingroup\$ For me, ordering the keys in the dictionary is usually the big thing that I waffle one way, and the other on. sometimes it makes sense to view the keys in insertion order. Sometimes, for human comprehension, it is easiest to view their alphabetical order. So, sorry, I do not have a suggestion for which should be default. \$\endgroup\$
    – J_H
    Feb 6, 2023 at 20:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.