psychopy.org | Reference | Downloads | Github

Execute code on ESCAPE

If this template helps then use it. If not then just delete and start from scratch.

Linux/Windows
PsychoPy version: 1.79/1.84
Standard Standalone? (y/n) If not then what?:y
What are you trying to achieve?:
I’m experienced with Python but not psychopy. I need to use my own class inside psychopy builder which I was able to do fine. I need it to close down gracefully though.

What did you try to make it work?:
I need to run the following at the beginning:
thread_server = ThreadServer()
and the following at the end:
thread_server.stop()

What specifically went wrong when you tried that?:
The above works except when I need to quit in the middle of the experiment by pressing ESC. When I do that, it seems psychopy hangs and doesn’t quit at all. To provide more detail, the ThreadServer class has a __del__() function that stops the thread and waits for it to join, but it seems this __del__() function is not called when I press ESC.

I have two questions:

  1. Is there anyway to call certain functions on ESC?
  2. why isn’t my class __del__() not called on quitting my experiment either normally or using ESC?

Thanks,
Aravind.

Hi, by default, on pressing the escape key, core.quit() will be called:

def quit():
    """Close everything and exit nicely (ending the experiment)
    """
    # pygame.quit()  # safe even if pygame was never initialised
    logging.flush()
    for thisThread in threading.enumerate():
        if hasattr(thisThread, 'stop') and hasattr(thisThread, 'running'):
            # this is one of our event threads - kill it and wait for success
            thisThread.stop()
            while thisThread.running == 0:
                pass  # wait until it has properly finished polling

    sys.exit(0)  # quits the python session entirely

If your threading class inherits from threading.Thread, you probably only need to introduce a stop() method, which will simply call your current __del__() (or at least part of __del__()'s shutdown procedure), and additionally set an attribute self.running to non-zero (?!? is this a typo/bug ?!?) once the shutdown is completed.

Hi Aravind,
I had a similar issue: I wanted to execute some generic code on core.quit() calls.
I did some monkey patching. Note that it’s definitively bad practice, but it works.
I created an external module (let’s say utilities.py):

from psychopy import core

class Singleton(type):

    def __init__(self, *args, **kwargs):
        super(Singleton, self).__init__(*args, **kwargs)
        self.__instance = None
        
    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super(Singleton, self).__call__(*args, **kwargs)
        return self.__instance

class OnPsychoPyQuit(object):

    __metaclass__ = Singleton
    
    IGNORE_NO = 0
    IGNORE_ALL = 1
    IGNORE_CORE_QUIT = 2
    
    _IGNORE_VALUES = (IGNORE_NO, IGNORE_ALL, IGNORE_CORE_QUIT)
    
    def __init__(self, myQuitFunction = (core.quit,(),{})):
        super(OnPsychoPyQuit, self).__init__()
        self._functions = []
        self._myQuit = tuple(myQuitFunction)
        self._coreQuit = None
        self._ignoreQuit = self.IGNORE_NO
        self._stack = []

    def _quit(self):
        if self._ignoreQuit == self.IGNORE_ALL:
            return
        for f in self._functions:
            try:
                f[1](*f[2],**f[3])
            except Exception, e:
                print 'Error calling quit callback: %s' %str(e)
        if self._ignoreQuit != self.IGNORE_CORE_QUIT:
            self._myQuit[0](*self._myQuit[1], **self._myQuit[2])
    
    @property
    def coreQuit(self):
        return self._coreQuit
        
    @property
    def quitFunction(self):
        return self._myQuit
        
    def setQuitFunction(self, f, *args, **kwargs):
        self._myQuit = (f, args, kwargs)

    @property
    def ignore(self):
        return self._ignoreQuit
        
    @ignore.setter
    def ignore(self, v):
        if not v in self._IGNORE_VALUES:
            raise ValueError('Value not in %s' %str(self._IGNORE_VALUES))
        self._ignoreQuit = v
    
    def pushRegistry(self):
        if len(self._functions):
            self._stack.append((self._ignoreQuit, self._functions[:]))
            
    def popRegistry(self):
        if len(self._stack):
            self._ignoreQuit, self._functions = self._stack.pop(-1)
    
    def replace(self):
        if self._coreQuit == None:
            self._coreQuit = core.quit
            core.quit = self._quit
        
    def restore(self):
        if self._coreQuit != None:
            core.quit = self._coreQuit
         
    def register(self, name, cb, *args, **dargs):
        self._functions.append((name, cb, args, dargs))

    def unregister(self, *args):
        if len(args):
            functions = [ f for f in self._functions if f[0] not in args ]
            self._functions = functions
        else:
            self._functions = []

Then from inside a code component in the psychopy script, I just do:

from utilities.py import OnPsychoPyQuit

def myFunction(arg1, arg2):
     print 'User pressed ESCAPE: bailing out...'

OnPsychoPyQuit().replace()
OnPsychoPyQuit().register('My function', myFunction, arg1, arg2)
...
OnPsychoPyQuit().unregister('My function')

Note that you can also do:

OnPsychoPyQuit().pushRegistry()
OnPsychoPyQuit().register('My function1', myFunction1)
OnPsychoPyQuit().register('My function2', myFunction2)
OnPsychoPyQuit().register('My function3', myFunction3)
OnPsychoPyQuit().register('My function4', myFunction4)
...

OnPsychoPyQuit().popRegistry()

Hi Luca:

I tried Richard’s method and it worked well for me (using the thread mechanism in core.quit()). I did get to your method since the first worked and seemed like the right way to do it - not to mention your own disclaimer :). Thanks very much all the same!

Aravind.

Hi Richard:

Thanks for your reply. Your suggestion worked well - Thanks very much!

Aravind.

1 Like

xref:
https://github.com/psychopy/psychopy/issues/1287

The python standard library includes atexit (https://docs.python.org/2/library/atexit.html), which allows you to register functions to be called upon exit. I have never used it myself, but I think it could work well.

I don’t recall the origin of the running == 0 test. I suspect some threaded object we were tracking had options of 1 (yes), 0 (no) and -1 (stopped) which made 0 the appropriate test. But I don’t recall and I’m not even sure that functionality is useful now