7

I would like to define a log function that is called with a message followed by one or more variables to be printed out. So, something like the following:

log( "Oh no, error.", x, d)

log would be defined sorta like:

def log( msg, *arg):
    # Loop through arg, printing caller's variable's name and value.

This would log to a file the following:

Oh no, error.
    x = 5
    d = { foo: "Foo", goo: "Goo" }

Can this be done at all? I can print locals and arguments using inspect, but I don't know if I can iterate through values in the current frame, using the variable names of a previous frame. (locals in inspect.getargvalues(previousFrame) has the names, but lots of other names too.)

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Bitdiot
  • 1,506
  • 2
  • 16
  • 30
  • 1
    How do you uniquely determine the variable names? What if the caller had `y` in the namespace and it's value also happened to be `5`? What if the caller passes something like `d['foo']`? Do you need to recurse arbitrarily deeply into the objects in the caller's namespace? What if they pass the result of a function call? `log('message', x.bar())`? There are lots of cases that are pretty hard to resolve here... though you probably could inspect the stack and print out _all_ of the caller's local variables. – mgilson Sep 15 '15 at 16:19
  • *sigh* Yes, I guess you are right mgilson. So what should I do? Must I delete this post? Or maybe it's my wishful thinking, I just want a log function that logs the variables that I give it. This can't be done at all? So, if they pass it 5, it prints 5 = 5. if they pass it y, it prints y = 5. If they pass it an object, it prints object = str(object). – Bitdiot Sep 15 '15 at 16:22
  • @Bitdiot, why? It's not badly written or badly organized. And for me personally the question is interesting – ForceBru Sep 15 '15 at 16:26

2 Answers2

4

I think you can use something like this:

definition

def log(msg, **kwargs):
    print(msg)
    for key, value in kwargs.items():
        print('{0} = {1}'.format(key,value))

definition (if order is a must)

def log(msg, **kwargs):
    print(msg)
    for key, value in sorted(kwargs.items()):
        print('{0} = {1}'.format(key,value))

usage

msg='Oh no, error'
log(msg, x=5, y=6)

output

Oh no, error
y = 6
x = 5
jlnabais
  • 829
  • 1
  • 6
  • 18
  • 2
    This is good, since it doesn't utilize any black magic. Also [explicit is better than implicit](https://www.python.org/dev/peps/pep-0020/). – Yaroslav Admin Sep 15 '15 at 16:38
  • 2
    Note that the order of the arguments isn't necessarily what you expect... http://stackoverflow.com/questions/8977594/in-python-what-determines-the-order-while-iterating-through-kwargs – Karoly Horvath Sep 15 '15 at 16:44
  • You're right, I didn't took ordering into account here, I should have pointed the fact that dictionaries are unordered. I've edited the answer. – jlnabais Sep 15 '15 at 16:48
1

That could be very dirty and may not work from time to time (it does so on my machine), but it seems to do the trick.

Moreover, it doesn't need all these **keargs tricks. You just call log('Message',as,many,args,as,you,want) and that's all.

import inspect, gc

def log(msg,*args):
    #This gets the source code line that has to do with args
    #I mean, that calls log
    code=''.join(inspect.getframeinfo(gc.get_referrers(args)[0].f_back).code_context).strip()
    #get the arguments (except msg)
    c=code.split('log')[1].strip()
    c=c.replace('(','').replace(')','')
    c=c.split(',')[1:]
    if c[-1].endswith(';'):
        c[-1]=c[-1].replace(';','')

    for x in xrange(0,len(c)):
        print c[x],'=',args[x]


a=5; b='hello'

print 'test'

log('hello',a,b);

print 'hello'

Even if log is run from another function, it's OK.

ForceBru
  • 43,482
  • 10
  • 63
  • 98
  • I tried this. Really nifty looking code, but if the log is over multiple lines, it seems to trip up. I still like it, but I think I'm leaning toward the key=val solution. – Bitdiot Sep 16 '15 at 19:00