0

So, I'm messing around and created this pseudo State-Machine app sequencing pattern https://github.com/rebelclause/python_legs/blob/master/init_subclass_example.py extending this traceback method by a wide margin: https://stackoverflow.com/a/1690751/7955763

import traceback # for callable name
from functools import wraps

def tracename(orig_func):
    @wraps(orig_func)
    def wrapper(*args, **kwargs):
        (filename,line_number,function_name,text)=traceback.extract_stack()[-2]
        def_name = text[:text.find('=')].strip()
        print(def_name)
        return def_name
    return wrapper

Now, I realize it may not be the right method for the job of making a decorator; after all the traceback has to immediately follow the function whose caller name you want to get. I tried knowing this anyway, but now the fun is over. I'm not sure how I'd use it (even in the framework I've presented), but can someone answer as to how the decorator and the code to capture the caller name can be improved so it'll work as a decorator in a stack of decorators? And maybe how?

Edit: Added this while avoiding a coroutine problem...

import traceback # for callable name
from functools import wraps

# this should make you laugh, or not
def tracename(orig_func):
    @wraps(orig_func)
    def wrapper(*args, **kwargs):
        (filename,line_number,function_name,text)=traceback.extract_stack()[-2]
        def_name = text[:text.find('=')].strip()
#        print(def_name)
        return def_name
    return wrapper


class foo(object):
    ''' '''
    def __init__(self):
        pass

    @tracename
    def _goodbye(self):
        print("It's been a good run so far, but this decorator might be toast.")


print(foo()._goodbye()) # prints wrapper returned var def_name

foo()._goodbye() # sits and watches while we patiently wait?

# uncomment the print statement in the decorator, then re-run

# then comment out the decorator and run it

guess_who = foo()._goodbye()

print('Guess where def_name went :', guess_who) # would it freak you out if the comment printed, too?
Tim Pozza
  • 498
  • 6
  • 9
  • 1
    I'm not sure what you're asking here. But normally, a decorator's wrapper function will call the wrapped function somewhere with the passed-in args (or some transformed version of them) and return its result (possibly transformed). Yours just returns some string that it extracted from the stack frame of the caller of the function without ever calling it, so it's hard to see how you could usefully compose that with anything stacked above it. – abarnert Aug 02 '18 at 03:59
  • You can use it as is without altering the traceback index (-2) on a method of a class, and as it is, it will return the caller, but, for a reason I haven't dived into yet, it won't run the method's code. – Tim Pozza Aug 02 '18 at 16:54
  • I wrote an answer explaining how to get it to run the method's code—but if that's your actual question, please edit the question to say that, rather than leave it in a comment. Meanwhile, I still have no idea what you're trying to extract and print out here, but I took some guesses and added that to the answer as well. – abarnert Aug 02 '18 at 17:29

1 Answers1

0

Your actual question appears to be in this comment:

You can use it as is without altering the traceback index (-2) on a method of a class, and as it is, it will return the caller, but, for a reason I haven't dived into yet, it won't run the method's code.

The reason it doesn't run the method's code is simple: you don't call the method.

Normally, a decorator looks like this:

def deco(func):
    @wraps(func)
    def wrapped(*args, **kwargs):
        # possibly modify args, kwargs
        # do any other pre-call stuff
        result = func(*args, **kwargs)
        # possibly modify result
        # do any other post-call stuff
        return result
    return wrapped

Your decorator is missing the call to func, and it's also returning the string used to call the function, instead of the return value.

If you want it to act like a normal decorator, just do what a normal decorator does:

def tracename(orig_func):
    @wraps(orig_func)
    def wrapper(*args, **kwargs):
        (filename,line_number,function_name,text)=traceback.extract_stack()[-2]
        def_name = text[:text.find('=')].strip()
        print(def_name)
        return orig_func(*args, **kwargs)
    return wrapper

It's also worth noting that your function doesn't actually "capture the caller name". What it captures is the text of the calling statement, truncated at the first =—or, if there is no =, with the last character stripped off:

>>> @tracename
>>> def spam(n):
...     return n
>>> spam(n=1) # will truncate the "=1)"
spam(n
>>> spam(1) # will truncate the last character
spam(1
>>> print(spam(1)) # will truncate the last character
print(spam(1)
>>> x = spam(1) # will truncate the "= spam(1)"
x 

None of these examples, or anything else I can think of, even contains the caller name. The caller name is that function_name that you get and ignore.

Although really, unless you need to be compatible with Python 2.6 or something, you're probably better off using inspect than traceback here:

>>> def tracecallername(func):
...     @wraps(func)
...     def wrapped(*args, **kwargs):
...         frame = inspect.stack()[1]
...         print(frame.function)
...         # frame.code_context has the statement if you want that for something
...         return func(*args, **kwargs)
...     return wrapped
>>> @tracecallername
... def spam(n):
...     return n
>>> def eggs():
...     print(spam(1))
>>> eggs()
eggs
1

Meanwhile, if by "the caller" you mean the method receiver—the self instance the method was called on—there's a much easier way to do that, because the self is always explicitly passed as the first parameter in Python method calls:

def tracereceiver(func):
    @wraps(func)
    def wrapped(self, *args, **kwargs):
        print(self)
        return func(self, *args, **kwargs)
    return wrapped
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • You raise a number of good points and also lead to possible solutions through the clarity of the examples offered. The inspect function appears to disambiguate or abstract traceback, and while I know about it, your perspectives help. `eggs()` `brown_toast = spam(2)` `print('over easy: ', brown_toast) # 'brown_toast', the label as the frame capture` – Tim Pozza Aug 02 '18 at 19:53
  • The method in my example is called with an instance/self, re. foo()._goodbye(), foo() supplying 'self' to the method. – Tim Pozza Aug 02 '18 at 20:02