Print a variable's name and value
Asked Answered
K

14

128

When debugging, we often see print statements like these:

print x        # easy to type, but no context
print 'x=',x   # more context, harder to type
12
x= 12

How can write a function that will take a variable or name of a variable and print its name and value? I'm interested exclusively in debugging output, this won't be incorporated into production code.

debugPrint(x)    #  or
debugPrint('x')
x=12
Keffiyeh answered 14/8, 2015 at 1:22 Comment(9)
print ("%s=%s" % (name, eval(name))) (yuck) ?Proliferous
possible duplicate of How can you print a variable name in python?Dup
Don't do this! Use a debugger! :)Overreact
Also maybe #633627Proliferous
@jme, sadly, I'm not running my programs on computers with screens, so logging is my only recourse!Keffiyeh
Don't do this! Prove your code is correct! :)Athlete
@Dup I would have agreed with the duplicate subcase a day ago. But now I learnt about a feature that explicitly prints both variable name and value: https://mcmap.net/q/37538/-print-a-variable-39-s-name-and-value This is the risk of marking subcases as dupes :-)Shipwreck
See for example github.com/gruns/icecreamFactory
did you try foo = 1 bar = 2 print(f"{foo=} {bar=}") in python 3.8?Tiffa
P
33

You can just use eval:

def debug(variable):
    print variable, '=', repr(eval(variable))

Or more generally (which actually works in the context of the calling function and doesn't break on debug('variable'), but only on CPython):

from __future__ import print_function

import sys

def debug(expression):
    frame = sys._getframe(1)

    print(expression, '=', repr(eval(expression, frame.f_globals, frame.f_locals)))

And you can do:

>>> x = 1
>>> debug('x + 1')
x + 1 = 2
Proclitic answered 14/8, 2015 at 1:36 Comment(8)
Why the downvotes? If it's about the safety of eval, I don't see why that's a problem because this will never be in production code.Limicolous
very simple and working solution, exactly what i needShrapnel
Does not work in the case say: m = [1,2], debug(len(m)), it returns TypeError: len() takes exactly one argument (2 given)Pomfrey
This has been superseded in 3.8 by f-string '=' syntax, you should mention that.Maddux
For the "f-string '=' syntax," mentioned by @smci, see https://mcmap.net/q/37538/-print-a-variable-39-s-name-and-valueHydrotaxis
did you try foo = 1 bar = 2 print(f"{foo=} {bar=}") in python 3.8?Tiffa
@CharlieParker the answer is from 2015 ans Python 3.8 is from 2018.Armelda
Doesn't work if what you're trying to print isn't in global scope. ``` def foo(a): b = a + 3; debug("b") ``` Your debug function will fail when it attempts to eval("b"). Worse, if there is a "b" at global scope, your function will print that value instead of the value of b within the foo function invocation.Scallion
S
313

Python 3.8 f-string = syntax

It has arrived!

#!/usr/bin/env python3
foo = 1
bar = 2
print(f"{foo=} {bar=}")

output:

foo=1 bar=2 

Added in commit https://github.com/python/cpython/commit/9a4135e939bc223f592045a38e0f927ba170da32 "Add f-string debugging using '='." which documents:

f-strings now support =  for quick and easy debugging
-----------------------------------------------------

Add ``=`` specifier to f-strings. ``f'{expr=}'`` expands
to the text of the expression, an equal sign, then the repr of the
evaluated expression.  So::

  x = 3
  print(f'{x*9 + 15=}')

Would print ``x*9 + 15=42``.

so it also works for arbitrary expressions. Nice!

The dream: JavaScript-like dict keys from variable names

I find Python better than JavaScript in almost every sense, but I've grown to really like this JavaScript feature:

let abc = 1
let def = 2
console.log({abc, def})

works in JavaScript because {abc, def} expands to {abc: 1, def: 2}. This is just awesome, and gets used a lot in other places of the code besides logging.

Not possible nicely in Python currently except with locals: Python variables as keys to dict

Shipwreck answered 26/7, 2019 at 19:21 Comment(6)
Technically, it works, but print(f'{foo=}') makes a lot more punctuation than the OP's request for some(foo) or some('foo'). Looks silly, but the point is to have something very easy, and this solution easily gets punctuation wrong, hurting the effectiveness IMHO.Hydrotaxis
@StéphaneGourichon yes, this is especially effective for large expressions.Shipwreck
Additionally, you get a pylint(logging-fstring-interpolation).Circumstantiality
Just to add on top of it, the name of the feature is "self-documenting expressions:" docs.python.org/3/whatsnew/3.8.html#bpo-36817-whatsnewSeacoast
How do you that looping over, say a list of kwargs?Languedoc
@Languedoc hwargs is a dict, so print(f'{kwargs=}') gives something like kwargs={'a': 1, 'b': 2} by calling its __str__. What is missing?Shipwreck
P
33

You can just use eval:

def debug(variable):
    print variable, '=', repr(eval(variable))

Or more generally (which actually works in the context of the calling function and doesn't break on debug('variable'), but only on CPython):

from __future__ import print_function

import sys

def debug(expression):
    frame = sys._getframe(1)

    print(expression, '=', repr(eval(expression, frame.f_globals, frame.f_locals)))

And you can do:

>>> x = 1
>>> debug('x + 1')
x + 1 = 2
Proclitic answered 14/8, 2015 at 1:36 Comment(8)
Why the downvotes? If it's about the safety of eval, I don't see why that's a problem because this will never be in production code.Limicolous
very simple and working solution, exactly what i needShrapnel
Does not work in the case say: m = [1,2], debug(len(m)), it returns TypeError: len() takes exactly one argument (2 given)Pomfrey
This has been superseded in 3.8 by f-string '=' syntax, you should mention that.Maddux
For the "f-string '=' syntax," mentioned by @smci, see https://mcmap.net/q/37538/-print-a-variable-39-s-name-and-valueHydrotaxis
did you try foo = 1 bar = 2 print(f"{foo=} {bar=}") in python 3.8?Tiffa
@CharlieParker the answer is from 2015 ans Python 3.8 is from 2018.Armelda
Doesn't work if what you're trying to print isn't in global scope. ``` def foo(a): b = a + 3; debug("b") ``` Your debug function will fail when it attempts to eval("b"). Worse, if there is a "b" at global scope, your function will print that value instead of the value of b within the foo function invocation.Scallion
R
20

Use the latest f'{var = }' feature in Python3.8 for example:

>>> a = 'hello'
>>> print(f'{a = }')
a = 'hello'
Ragtime answered 4/1, 2021 at 19:30 Comment(1)
how could this be made into a function like 'debug(var)'?Bah
U
6
import inspect
import re
def debugPrint(x):
    frame = inspect.currentframe().f_back
    s = inspect.getframeinfo(frame).code_context[0]
    r = re.search(r"\((.*)\)", s).group(1)
    print("{} = {}".format(r,x))

This won't work for all versions of python:

inspect.currentframe()

CPython implementation detail: This function relies on Python stack frame support in the interpreter, which isn’t guaranteed to exist in all implementations of Python. If running in an implementation without Python stack frame support this function returns None.

Us answered 14/8, 2015 at 1:58 Comment(0)
B
5

Just developed the answer of @Padraic Cunningham to take arbitrary number of variables. I liked this method since it works just like print(x1, x2, x3) - no need to wrap var names in ''.

import inspect
import re

def prinfo(*args):
    frame = inspect.currentframe().f_back
    s = inspect.getframeinfo(frame).code_context[0]
    r = re.search(r"\((.*)\)", s).group(1)
    vnames = r.split(", ")
    for i,(var,val) in enumerate(zip(vnames, args)):
        print(f"{var} = {val}")
    
x1 = 1
x2 = 2
x3 = 3
prinfo(x1, x2, x3)

Output is:

x1 = 1
x2 = 2
x3 = 3
Besom answered 10/2, 2021 at 19:11 Comment(0)
D
3

I wrote the following to be able to type something like (at line 41 of file describe.py):

describe('foo' + 'bar')
describe(numpy.zeros((2, 4)))

and see:

describe.py@41 describe('foo' + 'bar') = str(foobar) [len=6]   
describe.py@42 describe(numpy.zeros((2, 4))) = ndarray(array([[0., 0., 0., 0.],
   [0., 0., 0., 0.]])) [shape=(2, 4)]

Here's how:

# Print the line and filename, function call, the class, str representation and some other info

# Inspired by https://mcmap.net/q/37621/-how-to-find-the-name-of-a-variable-that-was-passed-to-a-function
import inspect
import re


def describe(arg):
    frame = inspect.currentframe()
    callerframeinfo = inspect.getframeinfo(frame.f_back)
    try:
        context = inspect.getframeinfo(frame.f_back).code_context
        caller_lines = ''.join([line.strip() for line in context])
        m = re.search(r'describe\s*\((.+?)\)$', caller_lines)
        if m:
            caller_lines = m.group(1)
            position = str(callerframeinfo.filename) + "@" + str(callerframeinfo.lineno)

            # Add additional info such as array shape or string length
            additional = ''
            if hasattr(arg, "shape"):
                additional += "[shape={}]".format(arg.shape)
            elif hasattr(arg, "__len__"):  # shape includes length information
                additional += "[len={}]".format(len(arg))

            # Use str() representation if it is printable
            str_arg = str(arg)
            str_arg = str_arg if str_arg.isprintable() else repr(arg)

            print(position, "describe(" + caller_lines + ") = ", end='')
            print(arg.__class__.__name__ + "(" + str_arg + ")", additional)
        else:
            print("Describe: couldn't find caller context")

    finally:
        del frame
        del callerframeinfo

https://gist.github.com/HaleTom/125f0c0b0a1fb4fbf4311e6aa763844b

Daybreak answered 29/3, 2018 at 13:38 Comment(0)
M
1

For those who are not using python 3.8 yet, here is an alternative.

This is a modified, shorter version of the accepted answer from a closed 2009 duplicate question found here, (which was also copied with a mistake in the below Aug 14, '15, the mistake being the re contains the hard coded function name 'varname' instead of the function name shown 'getm'). Original found here: How can you print a variable name in python??

To explain the re below, the inspect.getframeinfo(inspect.currentframe(), f_back)[3] gives the function signature in a list

['                p(prev)\n']

Casting to str saves you from having to loop through the list of one item. The re looks for an '(' which has to be escaped, the next '(' is to create a group within the match to reference, then [^)] means any character not ')', the '^' means 'not' in this context, brackets [] mean match any character within, and the following '*' is a quantifier for 0 or more times. Then close the group with a ')', match the closing ')' and voila:

def p(x):
    import inspect
    import re
    m = re.search('\(([^)]*)\)',str(inspect.getframeinfo(inspect.currentframe().f_back)[3]))
    print(f' {m.group(1)}: {x}')

Does this work with 2.7? Wait here while I check ... No, seemingly not. I did see one or two other variations that didn't use inspect.getframeinfo(inspect.currentframe().f_back)[3], so perhaps one of those would work. You'd have to check the duplicates and comb through the answers. Also to caution, some answers said to beware of python interpreters that may not be compatible with various solutions. The above worked on

Python 3.6.4 (v3.6.4:d48ecebad5, Dec 18 2017, 21:07:28)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin

Maggio answered 29/6, 2020 at 7:26 Comment(0)
M
1

I do it like this in jupyterlab to get matlab like variable printing:

def debug(var):
    stack = traceback.extract_stack()
    filename, lineno, function_name, name = stack[-2]
    print(name[6:-1] , ':' , var)

usage:

x=1
debug(x)

yields:

x : 1

Actually the exact code I use, for typing convenience and cleanness of output format, in case you would like to use it the same exact way, is:

import traceback
def p(var):
    stack = traceback.extract_stack()
    filename, lineno, function_name, name = stack[-2]
    print( "{:<25}".format(name[2:-1]) ,  ':   ' , var)
Miscarriage answered 2/6, 2022 at 0:10 Comment(1)
Actually I call the function p instead of debug, for extra convenience. Then you just have to change the print line from: print(name[6:-1] , ':' , var) , to , print(name[2:-1] , ':' , var) . Enjoy!Miscarriage
N
0

Quite ugly , but does the job :

import inspect, re
def getm(p):
  for line in inspect.getframeinfo(inspect.currentframe().f_back)[3]:
    match = re.search(r'\bvarname\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)', line)
    if match:
      return match.group(1)
x=21
search = getm(x);
print (search , '=' , eval(search))
Neural answered 14/8, 2015 at 1:54 Comment(0)
A
0

A simple example would be:

def debugPrint(*expr):
    text = traceback.extract_stack()[-2][3]
    begin = text.find('debugPrint(') + len('debugPrint(')
    end = text.find(')',begin)
    text=[name.strip() for name in text[begin:end].split(',')]
    for t, e in text, expr:
        print(str(t) +  " = " + str(e))

Hope it helps!

Adagio answered 25/10, 2018 at 11:22 Comment(0)
C
0

I've just concocted a function like this that prints an arbitrary expression:

import inspect, pprint

def pp(n):
    print()
    print(n,"=")
    f=inspect.stack()[1].frame
    pprint.pprint(eval(n,f.f_globals,f.f_locals))

(I used a blank line before the name and a newline before the value 'cuz in my case, I needed to print large data structures. It's easier to read such an output with the line breaks.)

It's safe as long as you don't pass it untrusted input.

You might also be interested in my dump module. It prints all the object's fields in a human-readable form. Proved extremely useful for debugging.

Clevey answered 26/11, 2018 at 23:46 Comment(0)
P
0

Multiple variables (taking @Blender response one step further) :

def debug(variables, sep =''):
        vars = variables.split(',')
        for var in vars:
          print(var, '=', repr(eval(var)), end = sep)

Example:

import bumpy as np
gPrimeLinear = lambda z: np.ones(np.array(z).size)*z
gPrimeSigmoid = lambda z: 1./(1+np.exp(-z))*(1-1./(1+np.exp(-z)))
gPrimeTanh = lambda z: 1- np.tanh(z)**2
z = np.array([ 0.2, 0.4, 0.1])
debug("z, gPrimeLinear(z), gPrimeSigmoid(z), gPrimeTanh(z)", '\n')

This returns:

> z = array([0.2, 0.4, 0.1])  
> gPrimeLinear(z) = array([0.2, 0.4, 0.1]) 
> gPrimeSigmoid(z) = array([0.24751657, 0.24026075, 0.24937604]) 
> gPrimeTanh(z) = array([0.96104298, 0.85563879, 0.99006629])
Pomfrey answered 15/11, 2019 at 14:32 Comment(1)
A very "bumby" example ! ... ;-)Willock
R
0

When finding the name of a variable from its value,
you may have several variables equal to the same value,
for example var1 = 'hello' and var2 = 'hello'.

My solution to your question:

def find_var_name(val):

    dict_list = []
    global_dict = dict(globals())

    for k, v in global_dict.items():
        dict_list.append([k, v])
   
    return [item for item in dict_list if item[1] == val]

var1 = 'hello'
var2 = 'hello'
find_var_name('hello')

Outputs

[['var1', 'hello'], ['var1', 'hello']]
Rattlebrained answered 27/3, 2021 at 16:4 Comment(0)
S
0

You can use the icecream package, which is a bit more advanced than the print(f"{foo=} {bar=}") syntax that was introduced in Python 3.8. The icecream package is intended to be a replacement for people who use print statements for debugging purposes.

An example taken from the GitHub documentation:

from icecream import ic

def foo(i):
    return i + 333

ic(foo(123))

results in

ic| foo(123): 456
Semifinal answered 9/10, 2023 at 6:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.