why does global in a class behave differently when the class is within a function?

63 Views Asked by At

I'm trying to use ginput to register clicks on a map, and wanted to add action buttons using matplotlib widgets. In the following code, I can pass back the value of action to the main code by declaring it a global. If I click on the map, action=0, if I click on the button, action=1, as desired.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button

class Index:
    def test(self, event):
        global action
        action=1

# fake data
x=np.arange(30)
y=x**2
fig,ax=plt.subplots()
ax.plot(x,y)
callback = Index()
buttonname=['test']
colors=['white']
idx=[0.2]
bax,buttons={},{}

# set up list of buttons.
for i,col,button in zip(idx,colors,buttonname):
    bax[button] = plt.axes([0.92, i, 0.07, 0.07])
    buttons[button] = Button(bax[button],button,color=col,hovercolor='green')
    buttons[button].on_clicked(getattr(callback,button))

# register click on plot
while True:
     pts=plt.ginput(1)
     plt.pause(0.5)
     print("action is ",action)
     action=0 # reset 

But my confusion is, if I take the exact same code and place it in a def block, the value of action is no longer passed back, action is always zero.

def subtest():
    class Index:
        def test(self, event):
            global action
            action=1

    # fake data
    action=0
    x=np.arange(30)
    y=x**2
    fig,ax=plt.subplots()
    ax.plot(x,y)
    callback = Index()
    buttonname=['test']
    colors=['white']
    idx=[0.2]
    bax,buttons={},{}

    # set up list of buttons.
    for i,col,button in zip(idx,colors,buttonname):
        bax[button] = plt.axes([0.92, i, 0.07, 0.07])
        buttons[button] = Button(bax[button],button,color=col,hovercolor='green')
        buttons[button].on_clicked(getattr(callback,button))

    # register click on plot
    while True:
         pts=plt.ginput(1)
         plt.pause(0.5)
         print("action is ",action)
         action=0 # reset

res=subtest()

I'm very confused as to why this happens. I tried moving the class definition out into the main code but that didn't help. I'm happy for any kind of solution (e.g. passing action through an argument, which I have not understood how to do with widgets), as I think that the use of global is often frowned apon. But also a global -based solution is fine.

2

There are 2 best solutions below

1
On BEST ANSWER

action inside subtest is local to subtest, while action inside Index.test is global. Either declare action global in subtest, or use nonlocal in Index.test.

(I suspect there may be better solutions without globals, but since I'm not familiar with the GUI toolkit I'll leave that to someone else.)

0
On

you want to check out this article on closures and this other article on closures. I think you need to declare action outside of both your class and functions and then reference it with global in the class.

I don't think you need to use a class - you can get away with passing a variable, boolean, dictionary around functions and achieve the same thing.

You can use the "on_click" event to do what you want see this widget tutorial widget tutorial and this on matplotlib event handling

Here is an example piece of code using "global" to effect state changes. Better to pass around a variable or use the event to trigger whatever you want the state to effect.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button

state = False

def change_state(btn_action):
    #do something
    btn_action= not(btn_action)
    return btn_action

def grid(val):
    global state
    #do something
    state =change_state(state)
    print(state)

    ax.grid()
    fig.canvas.draw()

    #reset value
    state =change_state(state)
    print(f'resetting state to {state}')

# fake data
x=np.arange(30)
y=x**2

#create fig, ax
fig =plt.figure()
ax= fig.subplots()
p, = ax.plot(x,y)

# set up list of buttons.

buttonname=['test']
colors=['white']
idx=[0.2]
bax,buttons={},{}

for i,col,button in zip(idx,colors,buttonname):
    bax[button] = plt.axes([0.92, i, 0.07, 0.07])
    buttons[button] = Button(bax[button],button,color=col,hovercolor='green')
    buttons[button].on_clicked(grid)

#show plot
fig.canvas.draw()
plt.show()