25

This is the first example we meet when we face with decorators. But I'm not able to realize what exactly I would like.

A simple decorator named LOG. It should work like this:

@LOG
def f(a, b=2, *c, **d):
    pass

And the result should be something like:

f(1, pippo=4, paperino='luca')
===== Enter f =====
a = 1
b = 2
pippo = 4
paperino = luca
===== Exit f =====

Where every argument passed as a parameter to the function is showed with its value.

I discovered that the problem is harder than I thought, mainly because of the many different ways you can pass arguments to a function (think about tuples with *c or dictionaries with **d).

I tried a solution but I'm not sure it's correct. It' somethink like this:

def LOG(fn):
    import inspect
    varList, _, _, default = inspect.getargspec(fn)
    d = {}
    if default is not None:
        d = dict((varList[-len(default):][i], v) for i, v in enumerate(default))
    def f(*argt, **argd):
        print ('Enter %s' % fn).center(100, '=')
        d.update(dict((varList[i], v) for i, v in enumerate(argt)))
        d.update(argd)
        for c in d.iteritems():
            print '%s = %s' % c
        ret = fn(*argt, **argd)
        print 'return: %s' % ret
        print ('Exit %s' % fn).center(100, '=')
        return ret
    return f

I think it's not so easy as I expected, but it's strange I didn't found what I wanted on Google.

Can you tell me if my solution is ok? Or can you suggest a better solution to the problem I proposed?

Thank you to everybody.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
Luca
  • 251
  • 1
  • 3
  • 4
  • 2
    Does it work? If it works, then it's ok. If it does not, then you have a problem. Ask us a specific question about that problem. – Paul McMillan Oct 30 '09 at 09:06
  • Yes, you're right. Actually it seems to work. But as I wrote there are many complex ways you can pass arguments to a Python function: using tuples, dictionaries, default arguments, ... Actually even if it seems to work I'm not sure it's the correct implementations it will work in every case. – Luca Oct 30 '09 at 09:12
  • 2
    Having fallen prey to it myself, is this function, in the absolutely general form you're describing, necessary for you to continue developing your application? It sounds like the logging mechanism itself has drawn your focus, and I know from experience that can be a terrible productivity killer. – Paul McMillan Oct 30 '09 at 09:16
  • I absolutely agree with you. It's not important for me to go on with my work. Since it was not so important and I wasn't able to resolve the problem I moved it on the bottom of my todo list. But now it's a sort of exercise... I'd like to know if there is a general, elegant and simple solution to the problem I described. – Luca Oct 30 '09 at 09:23
  • Don't worry, my brain works like that too. And I suspect a lot of other peoples on this site... – mavnn Oct 30 '09 at 09:30
  • +1 for pippo e paperino, benvenuto :) – Andrea Ambu Oct 30 '09 at 11:38
  • love the italian metasyntactics. For non italian speakers, they are the names of goofy (pippo), pluto the dog (pluto) and donald duck (paperino) – Stefano Borini Oct 30 '09 at 12:36

4 Answers4

5

The only thing I noticed is that the dict((varList[i], v) for i, v in enumerate(argt)) construct you used twice is actually dict(zip(varList,argt)).

Other than that i only have meta-criticism: None of the above belong in a logfile.

Instead of going trough the logs to

  • see if functions are called with the correct args you use asserts and a debugger.
  • see if function return the correct results you write unittests.
Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
  • could you elaborate/give links on (1), please? – culebrón Oct 30 '09 at 11:20
  • 3
    Thank you for the useful zip function. I think you're right for the meta-criticism. I develop on a remote machine and I can't use a debugger. So this logging mecanism should be a way of debugging in an easy way. – Luca Oct 30 '09 at 11:21
  • 1
    To criticize the meta-criticism: if there is a bug in production, you will be happy to take any logs that you can get your hands on. So, all of that information belongs to the logs. – alisianoi Sep 01 '18 at 07:37
1

Everyhing is ok in your function. You seem to be lost with positional vs variable&keyword arguments.

Let me explain: positional arguments, a and b in your case, are obligatory (and may have default values). Other arguments are optional. If you want to make an argument obligatory or to have a default value, put it before *args and **kwargs. But remember that you can't supply an argument twice:

def x(a = 1, b = 2, *args, **kwargs):
    print a, b, args, kwargs

>>> x(3, 4, 5, b=6)
TypeError: x() got multiple values for keyword argument 'b'

There's another way, but not that readable, to have default values for arguments and have no positional args:

def x(*args, **kwargs):
    kwargs.updae({'a': 1, 'b': 2})

Your function that analyses the arguments is ok, though I don't understand why you write varargs and keywords into _. It passes arguments transparently:

def x(a = 1, b = 2, *args, **kwargs):
    print a, b, args, kwargs

def y(*args, **kwargs):
    x(*args, **kwargs)

>>> y(3, 4, 5, 6)
3 4 (5, 6) {}

>>> y(3, 4, 5, b=6)
TypeError: x() got multiple values for keyword argument 'b'
culebrón
  • 34,265
  • 20
  • 72
  • 110
0

I found your nice solution can slightly be improved upon, if you take into account that a general function theoretically can return an iterable, in which case an error is thrown.

Here is a solution for this:

Wrap print 'return: %s' % ret into an if statement:
if hasattr(ret, "__iter__"): print 'returned iterable' else: print 'return: %s' % ret

This way you don't either use a lot of time printing large iterables, but that can of course be modified according to ones needs. (Also a string doesn't have an __iter__ attribute, which is handy)

Matthias Michael Engh
  • 1,159
  • 2
  • 10
  • 25
0

A ready-made solution to this problem is offered in the Polog library.

Install it:

$ pip install polog

And use:

from polog import log, config, file_writer

config.add_handlers(file_writer())

@log("my message")
def f(a, b=2, *c, **d):
    pass

f(1, pippo=4, paperino='luca')

The result in a console:

[2022-10-30 21:42:32.704898] |    1    | SUCCESS |  AUTO  | "my message" | where: __main__.f() | time of work: 0.00001597 sec. | input variables: 1 (int), pippo = 4 (int), paperino = "luca" (str) | result: None (NoneType)
Evgeniy Blinov
  • 351
  • 3
  • 3