from pprint import pprint



"""
Python PyCodeObject 


 1 /* Bytecode object */
 2 typedef struct {
 3     PyObject_HEAD
 4     int co_argcount;             /* #arguments, except *args */
 5     int co_kwonlyargcount;       /* #keyword only arguments */
 6     int co_nlocals;              /* #local variables */
 7     int co_stacksize;            /* #entries needed for evaluation stack */
 8     int co_flags;                /* CO_..., see below */
 9     PyObject *co_code;           /* instruction opcodes */
10     PyObject *co_consts;         /* list (constants used) */
11     PyObject *co_names;          /* list of strings (names used) */
12     PyObject *co_varnames;       /* tuple of strings (local variable names) */
13     PyObject *co_freevars;       /* tuple of strings (free variable names) */
14     PyObject *co_cellvars;       /* tuple of strings (cell variable names) */
15     /* The rest doesn't count for hash or comparisons */
16     unsigned char *co_cell2arg;  /* Maps cell vars which are arguments. */
17     PyObject *co_filename;       /* unicode (where it was loaded from) */
18     PyObject *co_name;           /* unicode (name, for reference) */
19     int co_firstlineno;          /* first source line number */
20     PyObject *co_lnotab;         /* string (encoding addr<->lineno mapping) See
21                                     Objects/lnotab_notes.txt for details. */
22     void *co_zombieframe;        /* for optimization only (see frameobject.c) */
23     PyObject *co_weakreflist;    /* to support weakrefs to code objects */
24 } PyCodeObject;
"""


def foo():
    def bar():
        print spam

    spam = 'eggs'

    return bar

b = foo()


# code project, a code object is generated by the Python compiler and intepreted by the interpreter.
# It contains information that this interpreter needs to do its job.
# print foo.__code__

# 1. co_code is a PyObject (instruction opcodes, VM bytecode), belong to PyCodeObject
# pprint(foo.__code__.co_code)

# 2. co_cellvars, is a tuple containing the names of all variables in the function
#    that are also used in a nested function

# co_cellvars is a tuple containing the names of local variables that are referenced by nested functions.
# print foo.__code__.co_cellvars
# 3. co_freevars, type is tuple, records closure variable name
# print foo.__code__.co_freevars

'''
print foo.__code__.co_consts
print foo.__code__.co_names
print foo.__code__.co_name

print foo.__code__.co_argcount
# print foo.__code__.co_kwonlyargcount (Since Python 3)

# co_nlocals is the number of local variables used by the function (including arguments).
print foo.__code__.co_nlocals

# co_varnames is a tuple containing the names of the local variables (starting with the argument names).
print foo.__code__.co_varnames

print foo.__code__.co_stacksize
'''


# 4. first line number of PyCodeObject
# print foo.__code__.co_firstlineno


# cell project
# print b.__closure__
msg = "Function: [{}], Attr: [{}], Value: [{}]"

def print_all(func, *args):
    print '**************************************'

    for attr in args:
        print msg.format(func.__name__, attr, getattr(func.__code__, attr))

    print '**************************************\n'

def print_code_obj(code_obj, *args):
    print '**************************************'

    for attr in args:
        print msg.format('lambda', attr, getattr(code_obj, attr))

    print '**************************************\n'


def foo_1(a, b, c):
    return a + b + c;

# print_all(foo_1, 'co_cellvars', 'co_freevars', 'co_consts', 'co_argcount', 'co_nlocals', 'co_varnames', 'co_names')

def foo_2(a, b, *args, **kwargs):
    return 5

# print_all(foo_2, 'co_cellvars', 'co_freevars', 'co_consts', 'co_argcount', 'co_nlocals', 'co_varnames', 'co_names')

def foo_3(x, y):    
    z = 123
    for i in range(10):
        z = z + i
    g = 'haha'
    return x + y, g
# co_nlocals is the number of local variables used by the function (including arguments).
# co_varnames is a tuple containing the names of the local variables (starting with the argument names).
# print_all(foo_3, 'co_varnames', 'co_nlocals', 'co_names')
# print_all(foo_3, 'co_argcount', 'co_consts', 'co_cellvars', 'co_freevars')


def foo_cell():
    x = 123
    b = 50
    def sub_foo():
        a = x
        a = b
        y = 1234
    sub_foo()
    return sub_foo

# co_name is the name of code_block, generally is the function name or class name
# print_all(foo_cell, 'co_varnames', 'co_nlocals', 'co_names', 'co_name')
# co_cellvars is a tuple containing the names of local variables that are referenced by nested functions.
# print_all(foo_cell, 'co_cellvars', 'co_freevars', 'co_consts')

# print_all(foo_cell(), 'co_varnames', 'co_nlocals', 'co_names', 'co_name')

# The free variable is a variable which refers to the value in cell variables of the outside function.
# print_all(foo_cell(), 'co_cellvars', 'co_freevars', 'co_consts')

# closure is a tuple, which record all the cells
# print foo_cell().__closure__[0].cell_contents
# print foo_cell().__closure__[1].cell_contents


def add_log_4(func):

    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        logging.warning('%s is running' % func.__name__)
        return result

    return wrapper

@add_log_4
def foo_4(name):
    print 'I am %s' % name
    return 1

# print_all(add_log_4, 'co_varnames', 'co_nlocals', 'co_names', 'co_name')
# print_all(add_log_4, 'co_cellvars', 'co_freevars', 'co_consts')

# print_all(foo_4, 'co_varnames', 'co_nlocals', 'co_names', 'co_name')
# print_all(foo_4, 'co_cellvars', 'co_freevars', 'co_consts')
# print foo_4.__closure__[0].cell_contents
# print_all(foo_4.__closure__[0].cell_contents, 'co_varnames', 'co_cellvars', 'co_freevars', 'co_consts')



def closure_pitfall():
    sub_f_list = []
    for i in range(5):
        sub_f_list.append(lambda: pprint(i))

    for f in sub_f_list:
        f()
    return sub_f_list

funcs = closure_pitfall()

print_all(closure_pitfall, 'co_varnames', 'co_nlocals', 'co_names', 'co_name')
print_all(closure_pitfall, 'co_cellvars', 'co_freevars', 'co_consts')

print_code_obj(closure_pitfall.__code__.co_consts[2], 'co_varnames', 'co_nlocals', 'co_names', 'co_name')
print_code_obj(closure_pitfall.__code__.co_consts[2], 'co_cellvars', 'co_freevars', 'co_consts')

for f in funcs:
    print f.__code__.co_freevars
    print f.__closure__[0].cell_contents