Search code examples
pythonmatplotlibmatplotlib-widgetginput

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


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.


Solution

  • 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.)