38

I have a signal handler to handle ctrl-c interrupt. If in the signal handler I want to read a variable set in my main script, is there an alternative to using a "global" statement when setting the variable?

I don't mind doing this, but read this post (Do you use the "global" statement in Python?) in which someone commented that there should be no reason to ever use global.

What is the alternative in this case?

My code looks like this:


def signal_handler(signal, frame):
    print "in sig handler - g_var=%s" % g_var

def main():
    global g_var
    g_var = "test"

    time.sleep(120)


if __name__ == '__main__':
    signal.signal(signal.SIGINT, signal_handler)
    main()
Community
  • 1
  • 1
Joe Watkins
  • 1,593
  • 4
  • 15
  • 25

5 Answers5

38

You can use a closure as the signal handler that acquires its state from the main script:

import signal
import sys
import time

def main_function():

    data_for_signal_handler = 10

    def signal_handler(*args):
        print data_for_signal_handler
        sys.exit()

    signal.signal(signal.SIGINT, signal_handler) # Or whatever signal

    while True:
        data_for_signal_handler += 1
        time.sleep(0.5)

if __name__ == '__main__':
    main_function()
Henry Gomersall
  • 8,434
  • 3
  • 31
  • 54
  • OK, I think my problem is that the signal handler is defined outside the main function. I don't have a good reason to do this, so think I just need to move it – Joe Watkins Sep 11 '12 at 14:13
  • I guess not having a good reason is generally why global variables can be moved to a more restricted scope! :) – Henry Gomersall Sep 11 '12 at 14:32
  • 3
    Modifying a variable from the outer scope does not work as one might expect. A workaround is to pass a reference, like a dictionary that has a reference to the variable to be modified. – alexei Jul 15 '15 at 23:52
  • 2
    @alexei or in python3 use `nonlocal`. – Henry Gomersall Jul 16 '15 at 08:03
  • @alexei is correct, local variables cannot be modified across scope. You can READ it, just not modify it. I don't know why the closure example is upvoted - the closure var is still a local and will disappear when the handler exits. @henry - yes using the `nonlocal` keyword will find the variable in larger scope so that is a fix without making the variable global. I like @Maxim class example as a good/clean way to use a variable across methods.... – dturvene Feb 25 '22 at 16:59
37

You can use partial to create a "closure".

import signal
from functools import partial

def signal_handler(g_var, signal, frame):
    print "in sig handler - g_var=%s" % g_var

def main():
    g_var = "test"
    signal.signal(signal.SIGINT, partial(signal_handler, g_var))

    time.sleep(120)


if __name__ == '__main__':
    main()
Ale
  • 1,315
  • 9
  • 19
12

Within the object-oriented paradigm (OOP) it's quite convenient to use lambdas for that purpose. Using lambdas you could pass some additional context (like a self reference) and/or get rid of the unused arguments (signal, frame).

import time
import signal

class Application:

    def __init__( self ):
        signal.signal( signal.SIGINT, lambda signal, frame: self._signal_handler() )
        self.terminated = False

    def _signal_handler( self ):
        self.terminated = True

    def MainLoop( self ):        
        while not self.terminated:
            print( "I'm just doing my job like everyone else" )
            time.sleep( 3 )

app = Application()
app.MainLoop()

print( "The app is terminated, exiting ..." )
Maxim Ky
  • 341
  • 2
  • 6
1

If you're just reading the variable, there should be no need to make the variable "global"

def foo():
    print a
a = 3
foo()  #3

global is necessary to allow you to change the variable and have that change propagate into the module namespace.

If you want to pass some state to your callback without using global, the typical way to do this us to use an instance method as the callback:

class foo(object):
     def __init__(self,arg):
         self.arg = arg
     def callback_print_arg(self):
         print self.arg

def call_callback(callback):
    callback()

a = foo(42)
call_callback(a.callback_print_arg) #42
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • It is clear to me he wasnt talking about the "global" Python keyword, but the requirement of having the variable in global scope (defined in the module scope). This answer is not helpful. – Alan Evangelista Jun 30 '16 at 14:14
1

You can access outer-scope variables from within an inline-defined function, like so:

my_values = {'foo':'bar'}
def handler(signum, frame):
    for key,val in my_values.items():
        print key,val
    my_values['bat']='baz'
    #remember to use mutable types, like dicts or lists

signal.signal(signal.SIGINT, handler)
John Fink
  • 313
  • 1
  • 4
  • as just commented on a similar response: I think my problem is that the signal handler is defined outside the main function. I don't have a good reason to do this, so think I just need to move it – Joe Watkins Sep 11 '12 at 14:15
  • how would this work if handler() was defined in your own module? Is there a reason to do so? Just curious. – jouell Sep 30 '19 at 01:31