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()
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()
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()
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 ..." )
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
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)
Related
I am trying to write a class to handle signals using the signal python module. The reason for having a class is to avoid the use of globals. This is the code I came up with, but unfortunately it is not working:
import signal
import constants
class SignalHandler (object):
def __init__(self):
self.counter = 0
self.break = False
self.vmeHandlerInstalled = False
def setVmeHandler(self):
self.vmeBufferFile = open('/dev/vme_shared_memory0', 'rb')
self.vmeHandlerInstalled = True
signal.signal(signal.SIGUSR1, self.traceHandler)
signal.siginterrupt(signal.SIGUSR1, False)
#...some other stuff...
def setBreakHandler(self):
signal.signal(signal.SIGINT, self.newBreakHandler)
signal.siginterrupt(signal.SIGINT, False)
def newBreakHandler(self, signum, frame):
self.removeVMEHandler()
self.break = True
def traceHandler(self, signum, frame):
self.counter += constants.Count
def removeVMEHandler(self):
if not self.vmeHandlerInstalled: return
if self.vmeBufferFile is None: return
signal.signal(signal.SIGUSR1, signal.SIG_DFL)
self.vmeHandlerInstalled = False
On the main program I use this class in the following way:
def run():
sigHandler = SignalHandler()
sigHandler.setBreakHandler()
sigHandler.setVmeHandler()
while not sigHandler.break:
#....do some stuff
if sigHandler.counter >= constants.Count:
#...do some stuff
This solution is not working, as it appears that the handler for the signal.SIGUSR1 installed in the setVmeHandler method never gets called.
So my question is: is it possible to handle signal inside a class or shall I use globals?
To answer your question, I created the following simple code:
import signal
import time
class ABC(object):
def setup(self):
signal.signal(signal.SIGUSR1, self.catch)
signal.siginterrupt(signal.SIGUSR1, False)
def catch(self, signum, frame):
print("xxxx", self, signum, frame)
abc = ABC()
abc.setup()
time.sleep(20)
If I run it:
python ./test.py
Then in another window send a USR1 signal:
kill -USR1 4357
The process prints the expected message:
('xxxx', <__main__.ABC object at 0x7fada09c6190>, 10, <frame object at 0x7fada0aaf050>)
So I think the answer is Yes, it possible to handle signal inside a class.
As for why you code doesn't work, sorry, I have no idea.
I got a similar problem as toti08, referring to setVmeHandler(self), and found out the handler must have matching parameters i.e. (self, signum,frame).
I change a global variable in a signal handler and poll for it in the main program. But the value does not change in the main thread.
Is there a qualifier that I need to use to make it a volatile (like in Java) variable?
Here's the program:
test.py
import time
import signal
def debug():
closeSession = False
def sigint_handler(signal, frame):
global closeSession
print('Breaking the poll...')
closeSession=True
signal.signal(signal.SIGINT, sigint_handler)
# Start a program...
while not closeSession:
time.sleep(1)
print('Polling... closeSession = %r' % closeSession)
print('Exiting! Bye.')
# Sent 'quit' to stdin of the program
if __name__ == "__main__":
debug()
sigint_handler() gets called whenever I press Ctrl + C but the new value of closeSession is not used in the main thread.
I get the following output:
$ python test.py Polling... closeSession = False Polling...
closeSession = False
I press Ctrl + C
^CBreaking the poll... Polling... closeSession = False
Press Ctrl + C, again
^CBreaking the poll... Polling... closeSession = False
Press Ctrl + C, again
^CBreaking the poll... Polling... closeSession = False
Polling... closeSession = False
The problem is scope.
Inside the debug() function, you didn't declare closeSession as a global, which means that you have two variables called closeSession. One global and one scoped within the debug() function. And inside the sigint_handler() function, you've explicitly instructed to use global one, which is shadowed by the scoped one in the outer function.
You can solve this by declaring global before assignment in debug():
def debug():
global closeSession
closeSession = False
...
By the way, your code does not work on windows, it throws a IOError because the sleep function is interrupted. A workaround that worked for me is:
...
while not closeSession:
try:
time.sleep(1)
except IOError:
pass
print('Polling... closeSession = %r' % closeSession)
...
It's not pretty but it works.
You have to set global closeSession before accessing the variable, else you're creating a local variable with the same name and the loop will never end.
Try this:
import time
import signal
def debug():
global closeSession # <-- this was missing
closeSession = False
def sigint_handler(signal, frame):
global closeSession
print('Breaking the poll...')
closeSession=True
signal.signal(signal.SIGINT, sigint_handler)
# Start a program...
while not closeSession:
time.sleep(1)
print('Polling... closeSession = %r' % closeSession)
print('Exiting! Bye.')
# Sent 'quit' to stdin of the program
if __name__ == "__main__":
debug()
I am trying to use the python threading module. As I am sysadmin, I struggle a little bit when developing; and this concept is kind of new for me. I launch two threads and I want to stop them, when the main thread sets a flag to False:
class My_Thread( threading.Thread):
def __init__(self, thread_id, thread_name, count):
threading.Thread.__init__(self)
self.thread_id = thread_id
self.thread_name = thread_name
self.count = count
def run(self):
do_job(self.thread_name, self.thread_id, self.count)
def do_job(t_name, t_id, count):
while not get_kill():
print "It is "+str(time.time())+" and I am "+str(t_name)
print get_kill()
time.sleep(count)
kill = False
def get_kill():
return kill
def set_kill(state):
kill = state
if __name__ == '__main__':
a = My_Thread(1, "Thread-1", 2)
b = My_Thread(2, "Thread-2", 1)
a.start()
b.start()
while(True):
try:
pass
except KeyboardInterrupt,ki:
set_kill(True)
sys.exit(0)
But the value is never read as changed in both threads and they don't exit. Why is this value not properly read from threads?
The problem
In set_kill(), you are creating a new local variable kill setting it to state, and returning from the function. You are not actually updating the value of kill in the global scope.
To do that, you would need to have:
def set_kill(state):
global kill
kill = state
A better way
Using globals like that is generally considered bad practice, you probably want to convert your kill variable and functions into an object, to encapsulate that data and behaviour together:
class Kill(object):
kill = False
def get(self):
return self.kill
def set(self, value):
self.kill = value
Which you would use like this:
class MyThread(Thread):
def __init__(self, thread_id, thread_name, count, kill):
self.kill = kill
...
def do_job(self, ...):
while not self.kill.get():
...
if __name__ == '__main__':
kill = Kill()
a = My_Thread(1, "Thread-1", 2, kill)
b = My_Thread(2, "Thread-2", 1, kill)
...
kill.set(True)
I have a program that block reading with select,but i want it to return gracefully with sending a SIGINT signal. and process that signal with a handler. unfortunately, the block program does not return from select system call, instead it receives SIGINT and interupt.
can anybody help me with figuring out what is the problem? thanks a lot.
import select
import socket
import signal
class Test(object):
def __init__(self):
self._read, self._write = socket.socketpair()
def wait(self):
readable = [self._read.fileno()]
return select.select(readable, [], readable)
def wakeup(self):
self._write.send('1')
t = Test()
def handler(signum, frame):
t.wakeup()
signal.signal(signal.SIGINT, handler)
if __name__ == '__main__':
print t.wait()
print 'over.'
You misinterpreted the program's behaviour: it did return from the select system call, but with the error 'Interrupted system call' that you have to handle.
import errno
import select
import socket
import signal
class Test(object):
def __init__(self):
self._read, self._write = socket.socketpair()
def wait(self):
readable = [self._read.fileno()]
try:
return select.select(readable, [], readable)
except select.error as e:
if e.args[0] == errno.EINTR:
return
else:
pass
def wakeup(self):
self._write.send('1')
t = Test()
def handler(signum, frame):
t.wakeup()
signal.signal(signal.SIGINT, handler)
if __name__ == '__main__':
print t.wait()
print 'over.'
I have a class, 'Listener', that connects callbacks to D-Bus signals. The callbacks and signal names are provided by another class, 'Client'. If the callbacks provided by Client are passed to connect_to_signal (on dbus.Interface) as the callbacks to use when signals are received, everything works as expected, i.e. the callback method in class Client is called when the connected signal is received.
However, if I want to 'intercept' the signal and evaluate the payload before proceeding to call the Clients callback, I figured I could use a lambda expression and pass it to the connect_to_signal method, as in the example below:
import dbus
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
from gi.repository import GObject
class Client(object):
def __init__(self):
bus = dbus.SystemBus()
obj = bus.get_object("org.freedesktop.UDisks", "/org/freedesktop/UDisks")
interface = dbus.Interface(obj, "org.freedesktop.UDisks")
listener = Listener()
signals_and_callbacks = {"DeviceAdded": self.device_added,
"DeviceChanged": self.device_changed}
listener.listen_to_signals(interface, signals_and_callbacks)
def device_added(self, payload):
print "in device_added ", payload
def device_changed(self, payload):
print "in device_changed ", payload
class Listener(object):
def listen_to_signals(self, interface, signals_and_callbacks):
for signal, callback in signals_and_callbacks.items():
cb = lambda x: self.signal_cb(x, callback)
interface.connect_to_signal(signal, cb)
def signal_cb(self, opath, subscriber_cb):
print subscriber_cb
subscriber_cb(opath)
if __name__ == "__main__":
client = Client()
mainloop = GObject.MainLoop()
mainloop.run()
But this does not work as intended. The signals gets connected, in this case the code reacts to both 'DeviceAdded' and 'DeviceChanged', but only the last callback added gets called. If I only connect one signal the behaviour is as expected, but as soon as I connect more than one signal, passing lambda expressions as callbacks, both signals triggers the call to the last callback added.
Does anyone have any idea what's going on here?
The basic problem is Python's scoping rules and how you set up the callback.
This example illustrates your problem (and the solution):
def test1():
print("test1")
def test2():
print("test2")
def caller(name, fn):
print("calling function with name: {}".format(name))
fn()
class Driver(object):
def __init__(self):
self.signals = []
def connect_to_signal(self, name, what_to_call):
self.signals.append((name, what_to_call))
def run(self):
for name, signal in self.signals:
signal(1)
def main():
signals = {'test1':test1, 'test2':test2}
d = Driver()
for signal, callback in signals.items():
cb = lambda x: caller(signal, callback)
#cb = lambda x,s=signal,c=callback: caller(s, c) # TRY THIS INSTEAD!
d.connect_to_signal(signal, cb)
d.run()
if __name__ == '__main__':
main()
If you run this function as-is, you get the following:
calling function with name: test2
test2
calling function with name: test2
test2
If you comment out the line starting cb = lambda x, and uncomment the following line, you now get the desired:
calling function with name: test1
test1
calling function with name: test2
test2
The reason is that you need to capture the variables in your lambda expression, or their values will just be what they are at the end of your loop.