frame.f_locals['__class__'] -- When does it (not) exist and why?

2014-11-15 Thread kevinarpe
Hello,

I am CPython 3.4+ user on Linux.

I am writing a little library for myself to improve the traceback module -- 
print_exc() and friends.  I want to include the module name, class name (if 
possible), and function name.

Some background: traceback.print_exc() iterates through traceback objects 
returned by sys.exc_info()[2].  traceback.tb_frame holds each stack frame.  (I 
call this 'frame' below.)

My improved library nearly works, but I noticed a strange corner case around 
frame.f_locals['__class__'].

When super().__init__() is called, a 'magic' local appears in frame.f_locals 
called '__class__'.  Helpfully, it has the correct class for the context, which 
will differ from type(self).  (I discovered this magic local by poking around 
in the debugger.  I am unable to find any official documentation on it.)

Here is the quirk: In the last class in a chain of super.__init__() calls, this 
magic local disappears.  So I have no idea the correct class for the context.  
I am stuck with frame.f_locals['self'].

How can I recover the correct class for the context in the last __init__() 
method?

I noticed if I chance the last class to inherit from object, the magic local 
'__class__' appears again.

A little code to demonstrate:

# do not subclass object here
def class X:
def __init__(self):
# frame.f_locals['__class__'] does not exist
pass

def class Y(X):
def __init__(self):
# frame.f_locals['__class__'] == Y
super().__init__()

def class Z(Y):
def __init__(self):
super().__init__()

# subclass object here
def class X2(object):
def __init__(self):
# frame.f_locals['__class__'] == X2
pass

def class Y2(X2):
def __init__(self):
# frame.f_locals['__class__'] == Y2
super().__init__()

def class Z2(Y2):
def __init__(self):
super().__init__()

Thanks,
Arpe
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: frame.f_locals['__class__'] -- When does it (not) exist and why?

2014-11-15 Thread kevinarpe
Apologies for previous code example.  Yes, the 'def class' should read: 'class'.

Writing a sample to better demonstrate the issue made me realize that super() 
is doing something special.  It is injecting the magic '__class__' local.

I should rephrase my question: How do I get the declaring class from from a 
traceback object?  Currently, I cannot see how to do it.  Magic local 
'__class__' is not always available.  And locals 'cls' and 'self' may come from 
subclasses.

Code sample:

import inspect
import sys


class X:
def __init__(self):
# Magic local '__class__' is missing
raise ValueError()

class Y(X):
def __init__(self):
super().__init__()

class X2:
def __init__(self):
# Calling super() here will 'inject' magic local '__class__'
super().__init__()
raise ValueError()

class Y2(X2):
def __init__(self):
super().__init__()

def main():
_main(lambda: Y())
_main(lambda: Y2())

def _main(func):
try:
func()
except:
(exc_type, exc_value, traceback) = sys.exc_info()
tb = traceback
while tb:
frame = tb.tb_frame
# See: code.co_freevars.  Sometimes magic '__class__' appears.
code = frame.f_code
lineno = frame.f_lineno
func_name = code.co_name
file_path = code.co_filename
module = inspect.getmodule(frame, file_path)
module_name = module.__name__
print("File: {}, Line: {}, Func: {}, Module: {}".format(file_path, 
lineno, func_name, module_name))
for name in ('__class__', 'self', 'cls'):
if name in frame.f_locals:
print("{}: '{}'".format(name, frame.f_locals[name]))
tb = tb.tb_next
print()

if __name__ == '__main__':
main()
-- 
https://mail.python.org/mailman/listinfo/python-list