3

So, the title pretty much says it all.

for instance let's look at the below example code:

## How can I obtain a dict/list (like locals()) of all the variables in second and/or third layer scopes via a command 
# coming from the first layer?
## Or another example would be how could I obtain the variables "locals() style" from the thirdlayer via a 
# command from the second layer?
# essentially can a parent function/class access a list/dict of a child function
# or class??

def firstLayer():
     a = 4.7
     q = locals()
     print(q)
     # local vars of 1st layer
     def secondlayer():
          b = 7
          r = locals()
          print(r)
          # local vars of 2nd layer
          def thirdlayer():
               c = False
               s = locals()
               i = globals()
               print('c:\n', c, "\nglobals from 3rd layer:\n\t", i)
              # local vars of 3rd layer
          thirdlayer()

     secondlayer()
firstLayer()

sample_var = globals()
print(sample_var)
# returns the list of global variables

to reiterate what I said in the comments in the code, essentially is their any way I can get a list of all the variables local to a 'child' scope? I know functions are shut off, but if their is no way to do this is their any more complicated code that could achieve this and I could integrate it into a function or class if necessary.

EDIT: To elaborate further; here's the situation i'm in.

def varsfunc():
    font1 = "Harlow Solid"
    grey = '#454545'
    font2 = 'Mistral'
    font3 = 'Italic 35px Times New Roman'
    pnk = 'pink'
    grn = 'green'
    return locals()

Essentially, I am creating a module and the user must create some type of function that they list all of they variables they would like to declare to be used to modify a css file. Essentially, I would like to allow the user to not have to type "return locals()". I want to achieve it by having the end-users wrap the above example function in a decorator that will do the equivalent of returning locals() of the exact scope I want. The decorator does not work for me because it is in an outer scope.

TO BE EVEN MORE CLEAR: I need a decorator/function that wraps another function(i.e. a decorator), that can access and create a list of a child element.

def module_decorator_func_thing():
     r = command_that_acts_like_locals()_but_for_child_scopes
     def user_var_list():
          font1 = 'green'
          font2 = 'pink'
     # back in "module_decorator_func_thing"'s scope
     print(r) # this variable should contain only a dict/list containing the
     # the following:
     # r = {'font1': 'green', 'font2': 'pink')

currently users need to do this:

def vars_func_container():
     font1 = 'green'
     font2 = 'pink'
     return locals() #  <---- I want the user to not have to type this and for 
    # a function decorator to take care of it instead possibly.

Info for @aguy and others wishing for more info. The dictionary/list that I am obtaining via your guys' tips will be sent to this function to do the real job of the program. (If I were to start using lists, i'd need to convert to a dictionary but that's no problem for me to solve.) The dict of variables is used with this function to "compile/compyle"(Pun on the word 'Python' + 'compile) and is insert in the "variables" parameter. e.g. you execute the function like this.

compyle("My sample title", return_stylesheet_from_func(*insert .css filename), 
return_variables_from_function(*insert function containing variables*), "**True/False to turn on compilation**", 
"**True/False to turn on annotations/suggestions**")

def compyle(title, style_sheet, variables, boolean=False, boolean2=True):
    """
    :param title: The name you wish your .css file to be named.
    :param style_sheet: The name of the multi-line string that will compose your .css file
    :param variables: The name of the dictionary containing your .pcss variables
    :param boolean: A.K.A the "Compiler Parameter" - Turns the compiler on or off
    :param boolean2: A.K.A the "Annotation Parameter" - Turns annotations on or off
    :return: returns compiled .pcss text as normal .css style text to be utilized with .html
    """
    # -----------------------------------
    file_name = title + ".css"
    replace_num = len(variables.keys())
    counter = replace_num
    content = style_sheet
    # -----------------------------------
    # add theme support with namedtuple's formatted to mimic structs in C/C++
    # this will be a major feature update as well as a nice way to allow the future prospect of integrating C/C++ into
    # the compiler. Info: https://stackoverflow.com/questions/35988/c-like-structures-in-python
    for k, v in variables.items():
        counter -= 1
        content = content.replace(k, v, replace_num)
        if counter == 0:
            break
        else:
            pass
    looped_content = str(content)
    id_content = looped_content.replace("hash_", "#")
    output = id_content.replace("dot_", ".")

    if boolean is True:
        if boolean2 is True:
            output = " /* --- Pyle Sheet --- */\n" + output
            with open(file_name, 'w') as writ:
                writ.write(output)
                writ.close()
                print('compiled successfully; The file was saved as ' + "\"" + file_name + "\".")
        elif boolean2 is False:
            pass
        else:
            logging.warning("An Error Occurred - see module, documentation, or online Q&A for assistance.")
    elif boolean is False:
        if boolean2 is True:
            print('compiled successfully; The file ' + "\"" + file_name + "\"" + "was not saved/created.")
        elif boolean2 is False:
            pass
        else:
            logging.warning("An Error Occurred - see module, documentation, or online Q&A for assistance.")
    else:
        logging.warning('An Error Occurred with the Compile Parameter (See: boolean in pyle_sheets source file) - \ '
                        'see module, documentation, or online Q&A for assistance.')
Michael Beyer
  • 85
  • 2
  • 10
  • 2
    Can you explain why you want to do this? Knowing what you are trying to achieve by doing this might help us figure out a solution. – Bill Feb 17 '18 at 05:18
  • check my update – Michael Beyer Feb 17 '18 at 17:56
  • 1
    Functions are for executing a task or calculation and returning a result not as a way to declare variables. In fact the variables in the function are supposed to be temporary and don't persist. It sounds like you want a 'receptacle' to allow the user of this module to declare a set of variables that your module will then use. I can think of two ways to do this. A dictionary of values or variables or a class. A class could just hold class variables if you do not want to have multiple instances. Would any of these ideas work in your context? – Bill Feb 17 '18 at 18:49
  • A dict of values was my old idea but, I was trying to improve it from that.. I guess I might just have to make the end user do more work but, nobody likes using a confusing module. – Michael Beyer Feb 18 '18 at 01:59
  • check my third example and new update; I feel like we are not exactly on the same page but, then again we might. Essentially I want users to not need a bottom return statement saying 'return locals()'. I'd like them to just make a function containing all the variable initialization. – Michael Beyer Feb 18 '18 at 02:07
  • This is certainly possible, and I'll post a solution, but is there a reason you want to use a function instead of a class? – Nathan Vērzemnieks Feb 18 '18 at 04:49
  • @NathanVērzemnieks a class would suffice but, I prefer a function by a slight degree. I feel like Functions are more user-friendly **in general** and this is meant to be a module used by others. I am aiming to make this module able to be used by beginners in Python. Some users may only know HTML/CSS(maybe Javascript) and this could be their first time using Python let alone any dynamic programming language at all. – Michael Beyer Feb 20 '18 at 03:04

2 Answers2

1

I can't see any way to do this without getting pretty deep; what follows is the simplest solution I've come up with.

how it works

Using the ast module, we go through the code of the given function and find all the assignments. These are evaluated in a given namespace and this namespace is returned.

the code

import ast
import functools
import inspect

def returnAssignments(f):
    @functools.wraps(f)
    def returner():
        assignments = dict()
        for node in ast.walk(ast.parse(inspect.getsource(f))):
            if isinstance(node, ast.Assign):
                exec(compile(ast.Module([node]), '<ast>', 'exec'),
                     globals(),
                     assignments)
        return assignments
    return returner

usage

from ra import returnAssignments

@returnAssignments
def foo():
    this = 'something'
    that = 37
    the_other = object()

print(foo())

output

rat@pandion:~/tmp$ python test.py
{'this': 'something', 'that': 37, 'the_other': <object object at 0x10205b130>}
Nathan Vērzemnieks
  • 5,495
  • 1
  • 11
  • 23
  • This works great aswell! I don't know who has the better answer lol. I shall do a coin toss in the morning. – Michael Beyer Feb 19 '18 at 10:38
  • I have to give you the award of best answer even though it was truly a draw. Your method results in all data types keeping their integrity and that is obviously something that could be a benefit even though I need to turn them all into strings anyways... In fact, this could help allow for pre-processor statements since the variable's rvalues could keep their data types until the moment I need to turn them into a string. – Michael Beyer Feb 20 '18 at 03:35
0

I wonder if such a crude solution as I provide here might be useful to you. Note that I haven't tested it on all cases, so it might be a bit rough. Also, it returns everything as a string, a behavior which you might want to further change.

Define the function:

def get_local_vars_from_function(f):
    import inspect
    s = inspect.getsourcelines(f)[0]
    d = {}
    for l in s:
        if '=' in l:
            var, val = l.split('=')
            var = var.strip()
            val = val.strip()
            d[var] = val
    return d

Then to use it:

In[91]: get_local_vars_from_function(user_var_list)
Out[91]: {'font1': "'green'", 'font2': "'pink'"}
Aguy
  • 7,851
  • 5
  • 31
  • 58
  • I actually WANT everything to become a string so that helps... Let me integrate this into the project and i'll get back with you! thank you. EDIT: Check out my last edit labeled for you. – Michael Beyer Feb 19 '18 at 09:53
  • With no modification, your answer works. But to be fair, let me evaluate Nathan V's answer as well to be fair lol. In all honetsly, I went though these suggestions from bottom to top so I suppose that wasn't quite fair lol. – Michael Beyer Feb 19 '18 at 10:09