Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Doctest and Decorators in Python

I was trying to use Python decorator to catch exceptions and log the exceptions.

import os.path
import shutil

class log(object):
    def __init__(self, f):
        print "Inside __init__()"
        self.f = f

    def __call__(self, *args):
        print "Inside __call__()"
        try:
            self.f(*args)
        except Exception:
            print "Sorry"

@log
def testit(a, b, c):
    print a,b,c
    raise RuntimeError()

if __name__ == "__main__":
    testit(1,2,3)

It works fine

Desktop> python deco.py 
Inside __init__()
Inside __call__()
1 2 3
Sorry

The issue is that when I tried to use for testing with doctest

@log
def testit(a, b, c):
    """
    >>> testit(1,2,3)
    """
    print a,b,c
    raise RuntimeError()

if __name__ == "__main__":
    import doctest
    doctest.testmod()

nothing seems to be happening.

Desktop> python deco2.py 
Inside __init__()

What's wrong with this?

like image 473
prosseek Avatar asked Dec 12 '22 06:12

prosseek


2 Answers

The decorated function (which is actually a class instance) doesn't get the __doc__ attribute of the original function (which is what doctest parses). You could just copy __doc__ over to the class instance, but ... Honestly, I don't really see the need for a class at all here -- you'd probably be better just using functools.wraps

import functools
def log(func):
    @functools.wraps(func)
    def wrapper(*args):
        try:
            return func(*args)
        except Exception:
            print "sorry"
    return wrapper
like image 193
mgilson Avatar answered Dec 14 '22 22:12

mgilson


You need to copy the docstring over to your decorator:

class log(object):
    def __init__(self, f):
        print "Inside __init__()"
        self.f = f
        self.__doc__ = f.__doc__

    def __call__(self, *args):
        print "Inside __call__()"
        try:
            self.f(*args)
        except Exception:
            print "Sorry"

The decorator replaces the decorated function, and only by copying over the docstring would that attribute be visible to anyone.

You could make use of functools.update_wrapper() here to do the copying of the docstring, as well as several other attributes, for you:

from functools import update_wrapper

class log(object):
    def __init__(self, f):
        print "Inside __init__()"
        self.f = f
        update_wrapper(self, f)

    def __call__(self, *args):
        print "Inside __call__()"
        try:
            self.f(*args)
        except Exception:
            print "Sorry"
like image 44
Martijn Pieters Avatar answered Dec 15 '22 00:12

Martijn Pieters