Search code examples
pythonglobal-variablesclass-variables

Python: Is it 'proper' to replace global variables with class variables


Please be kind with me, I'm a Python beginner :-)

Now, I see that the 'best practice' for writing Python programs would be to wrap the main code inside a 'main' function, and do the if "__main__" == __name__: test to invoke the 'main' function.

This of course results in the necessity of using a series of global statements in the 'main' function to access the global variables.

I wonder if it is more proper (or 'Pythonic', if you will) to gather the global variables into a custom class, say _v, and refer to the variables using _v. prefix instead?

Also, as a corollary question, would that have any negative impact to, let's say, performance or exception handling?


EDIT : The following is the general structure of the program:

paramset = {
    0: { ...dict of params... }
    1: { ...dict of params... }
    2: { ...dict of params... }
    }

selector = 0
reset_requested = False
selector_change = False

def sighup_handler(signal,frame):
    global reset_requested
    logger.info('Caught SIGHUP, resetting to set #{0}'.format(new_selector))
    reset_requested = True
    selector = 0

def sigusr1_handler(signal,frame):
    global selector
    new_selector = (selector + 1) % len(paramset)
    logger.info('Caught SIGHUP, changing parameters to set #{0}'.format(new_selector))
    selector = new_selector
    selector_change = True

signal.signal(signal.SIGHUP, sighup_handler)
signal.signal(signal.SIGUSR1, sigusr1_handler)

def main():
    global reset_requested
    global selector
    global selector_change
    keep_running = True
    while keep_running
        logger.info('Processing selector {0}'.format(selector))
        for stage in [process_stage1, process_stage2, process_stage3]
            err, result = stage(paramset[selector])
            if err is not None:
                logger.critical('Stage failure! Err {0} details: {0}'.format(err, result))
                raise SystemError('Err {0} details: {0}'.format(err, result))
            else:
                logger.info('Stage success: {0}'.format(result))
            if reset_requested:
                stage_cleanup()
                reset_requested = False
            else:
                inter_stage_pause()
                if selector_change:
                    selector_change = False
                    break
        selector = (selector + 1) % len(paramset)

Solution

  • There are enough pieces missing from the example code that reaching any firm conclusions is difficult.

    Event-driven approach

    The usual approach for this type of problem would be to make it entirely event-driven. As it stands, the code is largely polling. For example, sighup_handler sets reset_requested = True and the while loop in main processes that request. An event-driven approach would handle the reset, meaning the call to stage_cleanup, directly:

    def sighup_handler(signal,frame):
        logger.info('Caught SIGHUP, resetting to set #{0}'.format(new_selector))
        stage_cleanup()
    


    Class with shared variables

    In the sample code, the purpose of all those process_stages and cycling through the stages is not clear. Can it all be put in an event-driven context? I don't know. If it can't and it does require shared variables, then your suggestion of a class would be a natural choice. The beginnings of such a class might look like:

    class Main(object);
    
        def __init__(self):
            self.selector = 0
            self.selector_change = False
            signal.signal(signal.SIGHUP, self.sighup_handler)
            signal.signal(signal.SIGUSR1, self.sigusr1_handler)
    
        def sighup_handler(self, signal,frame):
            logger.info('Caught SIGHUP, resetting to set #{0}'.format(new_selector))
            stage_cleanup() 
            self.selector = 0
    
        def sigusr1_handler(self, signal,frame):
            new_selector = (selector + 1) % len(paramset)
            logger.info('Caught SIGHUP, changing parameters to set #{0}'.format(new_selector))
            self.selector = new_selector
            self.selector_change = True
    
        def mainloop(self):
            # Do here whatever polling is actually required.
    
    if __name__ == '__main__':
        main = Main()
        main.mainloop()
    

    Again, because the true purpose of the polling loop is not clear to me, I didn't try to reproduce its functionality in the class above.