4

How do i detect or capture change of value of a global variable in python

variable = 10
print(variable)
variable = 20 
# Detect changes to the value using a signal to trigger a function

UPDATE AST docs - GOOD INTRO https://greentreesnakes.readthedocs.io/en/latest/

7
  • 1
    I don't know if python can do this but tkinter use classes for this - ie. IntVar() uses get()/set() to work with data but it has also tracer(callback) to assign function(s) which will be executed when value is changed. Similar classes - Properties - uses Kivy. So you could create own classes with similar functionality. Commented Jan 10, 2020 at 3:58
  • 1
    Is this in a standalone script, or in a larger application? Because you can use async processes within a loop to send signals/flags to be captured on the next loop Commented Jan 10, 2020 at 4:15
  • Standalone script and I am looking to capture only variable value changes @Sparrow1029 Commented Jan 10, 2020 at 4:40
  • 1
    What if you wrap it all in a while loop and at the top of each loop detect if any have changed? Sorry I don't know more about the script you're trying to write, but generally you need to have a separate watcher/listener process of some kind in order to detect changes in variables, which usually ends up more complicated than a single script... Commented Jan 10, 2020 at 4:47
  • 1
    Would it be acceptable if you can only capture changes applied from other modules? e.g. in module a you have a global variable, and then in module b, import a; a.variable = 123. This specific case is more doable as you can override the module object that import a results in, and then override the __setitem__ of that object. At that point though you are really going out of your way to do something that maybe is not meant to be done Commented Jan 10, 2020 at 5:04

2 Answers 2

3

To my knowledge, it is not possible to generically capture the assignment of a global symbol in Python (At least in CPython where globals are stored in a dict in the module object, both are C types that cannot be monkey patched).

Here's a simple workaround that's a bit of a compromise. Use a wrapper object to store your monitored variables, and define __setattr__ to do whatever you want to do before (or after) setting an attribute.

class CaptureOnSetAttribute:
    def __setattr__(self, attr, value):
        # our hook to do something
        print(f'set value of {attr} to {value}')
        # actually set the attribute the normal way after
        super().__setattr__(attr, value)


wrapper_object = CaptureOnSetAttribute()

The compromise of course is that now instead of writing something like:

monitored_global = value

You must now write:

wrapper_object.monitored_attribute = value
Sign up to request clarification or add additional context in comments.

4 Comments

Ok. Any tips on how to modify the ast and object type class def? Second, I am struggling to find the class implementation of the str. Is it documented somewhere? I cant see the definition of object functions.
@Gary what do you mean by "ast" and modifying it? If you are using CPython, then then object, type, and str are all implemented in C, I think most of the relevant code is here github.com/python/cpython/tree/master/Objects. If you're talking about trying to modify the Python interpreter to suit your needs, I may be out of my depth here. It also is likely out of the scope of this question so you may want to open a new one to explore further
Ok got it. It's the c based cpython implementation. Where can I find working with the ast.Str class is what I asked? Any clean working example? 'import ast' ; 'ast.Str()'
@Gary I personally haven't used python's ast library, and from searching a bit it does seem quite obscure, however I was able to find a site of "The missing Python AST documentation", greentreesnakes.readthedocs.io/en/latest. The "examples" page may be of some help to you. As a side note, according to the official docs, as of Python 3.8, ast.Str and others are now deprecated, superseded by ast.Constant. I hope this helps you a bit
2

How about instrument the bytecode to add a print statement before each statement that stores to the global variable. Here is an example:

from bytecode import *

def instr_monitor_var(func, varname):
    print_bc = [Instr('LOAD_GLOBAL', 'print'), Instr('LOAD_GLOBAL', varname),
                Instr('CALL_FUNCTION', 1), Instr('POP_TOP')]

    bytecodes = Bytecode.from_code(func.__code__)
    for i in reversed(range(len(bytecodes))):
        if bytecodes[i].name=='STORE_GLOBAL' and bytecodes[i].arg==varname:
            bytecodes[i:i]=print_bc

    func.__code__=bytecodes.to_code()

def test():
    global a
    a = 1
instr_monitor_var(test, 'a')
test()

instr_monitor_var can instrument a function test so the global variable a will be printed out when its value is changed. Let me know if this works. Thanks!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.