capture change of global variable value in python

2020-04-12 07:23发布

问题:

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/

回答1:

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


回答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!