Search code examples
pythonpython-2.xpython-decorators

Decorators influencing sys._getframe(1)


a.py

import d

d.funcme('blah')

d.py

import sys
import Errors
def argcheck(in_=(), out=(type(None),)):
    def _argcheck(function):
        # do something here
        def __argcheck(*args, **kw):
            print '+++++++++ checking types before calling the func'
            # do something here
            res = function(*args, **kw)
            return res
        return __argcheck
    return _argcheck

@argcheck((str))       <-----
def funcme(name):
    try:
        f = sys._getframe(1)
    except ValueError, err:
        raise Errors.UserError(err)     # stack too deep
        filename, lineno = f.f_globals['__name__'], f.f_lineno
    print filename, lineno

OUTPUT without argcheck decorator (comment out the @argcheck((str))):

$ python a.py  
__main__ 3  

OUTPUT with argcheck decorator:

$ python a.py  
+++++++++ checking types before calling the func  
defines 9  

Questions:

  1. What's decorator doing so that it's changing the values for _getframe?

  2. How can I preserve the information so it captures the original information i.e __main__ 3 and not defines 9?


Solution

  • The problem is that yourfuncme()function is assuming it has been called directly rather indirectly through something else — such as a decorator. This could be fixed by changing its calling sequence and adding an additionaldepthkeyword argument with a default value which will be passed on to _sys._getframe(). With this scaffolding in place, the decorator can then override the default value. The following will print the same thing whether or not the decorator has been applied:

     1 import sys
     2 import Errors
     3 def argcheck(in_=(), out=(type(None),)):
     4     def _argcheck(function):
     5         # do something here
     6         def __argcheck(*args, **kw):
     7             print '+++++++++ checking types before calling the func'
     8             # do something here
     9             res = function(*args, depth=2, **kw)  # override default depth
    10             return res
    11         return __argcheck
    12     return _argcheck
    13
    14 @argcheck((str))
    15 def funcme(name, depth=1):  # added keyword arg with default value
    16     try:
    17         f = sys._getframe(depth)   # explicitly pass stack depth wanted
    18     except ValueError, err:
    19         raise Errors.UserError(err)     # stack too deep
    20
    21     filename, lineno = f.f_globals['__name__'], f.f_lineno
    22     print filename, lineno