3

I'm trying to figure out how to not use global variables for my application but I can't think of anything else.

I'm actually coding a web interface with the help of the Flask-SocketIO module to interact in real time with a music player.

This is a snippet of my code containing the play function (I think I only need one example and then I can adapt it for all the other functions):

from flask import Flask, render_template
from flask_socketio import SocketIO

app = Flask(__name__)
socketio = SocketIO(app)
isPlaying = False #This is the variable that I would like to avoid making global

@socketio.on('request_play')
def cycle_play():
    global isPlaying
    if isPlaying == True:
        socketio.emit('pause', broadcast=True)
        isPlaying = False
    else:
        socketio.emit('play', broadcast=True)
        isPlaying = True

if __name__ == '__main__':
    socketio.run(app, port=5001)

This is only a stripped down version of the code but I think it's enough to understand what I'm trying to accomplish.

I need to access that variable also from other functions and I need to do the same with the song name, duration and current time.

Thanks in advance for the help and sorry if my English is not clear.


Here is the solution that I used:

from flask import Flask, render_template
from flask_socketio import SocketIO

app = Flask(__name__)
socketio = SocketIO(app)

class Player():
    def __init__(self):
        self.isPlaying = False


    def cycle_play(self):
        if self.isPlaying == True:
            socketio.emit('pause', broadcast=True)
            self.isPlaying = False
        else:
            socketio.emit('play', broadcast=True)
            self.isPlaying = True


if __name__ == '__main__':
    player = Player()
    socketio.on('request_play')(player.cycle_play) #this is the decorator
    socketio.run(app, port=5001)
F. Rossi
  • 43
  • 2
  • 6
  • What are your concerns with global variables? If you have to access a variable from other functions, then it's going to have to be in the main environment. If you don't want the function directly interacting with it, you can put "def cycle_play(copy_of_global_variable)" at the beginning of the function, and "return(copy_of_global_variable)" at the end, and then you have to remember to call the function with "global_variable = cycle_play(global_variable)". – Acccumulation Oct 04 '17 at 14:40
  • @F.Rossi I have no experience with Flask+socketio but using process-global state in a web application is usally a big no-no. Either it's a per-user state and you want to manage it with sessions or it's a "shared" state and you want to manage it with some database (relational or not). – bruno desthuilliers Oct 05 '17 at 07:29

3 Answers3

3

You can use the user session to store such values. You can read up more about the session object here: flask.pocoo.org/docs/0.12/quickstart/#sessions.

from flask import session

@socketio.on('initialize')
def initialize(isPlaying):
    session['isPlaying'] = isPlaying

@socketio.on('request_play')
def cycle_play():
    # Note, it's good practice to use 'is' instead of '==' when comparing against builtin constants.
    # PEP8 recommended way is to check for trueness rather than the value True, so you'd want to first assert that this variable can only be Boolean.
    assert type(session['isPlaying']) is bool

    if session['isPlaying']: 
        socketio.emit('pause', broadcast=True)
        session['isPlaying'] = False
    else:
        socketio.emit('play', broadcast=True)
        session['isPlaying'] = True
suripoori
  • 311
  • 2
  • 10
  • Thanks for the information about the use of 'is', however maybe I'm missing something but is session a standard name for something? Because I get this error: NameError: name 'session' is not defined – F. Rossi Oct 04 '17 at 17:53
  • "# Note, it's good practice to use 'is' instead of '==' when comparing against builtin constants" => Nope, the good practice in this case is to not compare against anything, all python objects have a truth value when used in a boolean context so `if something: do_this()` IS the good practice here. You only use `is` when you want to compare objects _identity_ (are both names pointing to the same object). – bruno desthuilliers Oct 05 '17 at 07:17
  • 1
    This being said, if the state that has to be maintained is a "per-user" state then using sessions makes perfect sense indeed. @F.Rossi you need to import `session` to use it: http://flask.pocoo.org/docs/0.12/quickstart/#sessions – bruno desthuilliers Oct 05 '17 at 07:21
  • `if something is True` is just as bad as `if something == True`, which is a common slip. Just use `if something`. If you actually do need a Boolean value you can always use `bool(something)`. [Steps down from soapbox]. It would be helpful to add the missing import. – holdenweb Oct 05 '17 at 12:49
  • 1
    I upvoted this because it used knowledge I don't have to offer a much better solution than my perhaps hastily-accepted answer. If the questioner reads this: you might reconsider your choice (I believe you are allowed to change your accepted answer0. – holdenweb Oct 05 '17 at 12:51
  • Thanks @brunodesthuilliers for providing the link to the docs. While I agree that all python objects have a truth value, it may not be a good idea to just do `if session['isPlaying']:` in this particular case. Without more context, I don't know what values `isPlaying` could be. The `initialize` method could be called with a string value for example: "Song is not playing" or "Song file is corrupted". In this case, given the text, the caller might expect the `else` path to be taken in the `cycle_play` method. But, `if session['isPlaying']:` would actually resolve to true. – suripoori Oct 05 '17 at 13:53
  • @suripoori initializing what is expected to be a boolean value with anything but a boolean value is an error. If this is a system or sub-system boundary then the provided value should be sanitized or validated. – bruno desthuilliers Oct 05 '17 at 14:31
  • @brunodesthuilliers Certainly, if there was some documentation saying `isPlaying` can only be Boolean and if there were some assertion to verify that `isPlaying` is Boolean, then `if session['isPlaying']:` is the PEP8 recommended way to do it. – suripoori Oct 05 '17 at 18:31
0

The solution that suggests itself is to define a class which encapsulates both the state variable and the responses to its change. Since I'm not familiar with the details of Flask-SocketIO please treat this as pseduocode rather than something to be pasted in to a working program.

class PlayControl:
    def __init__(self, initial):
        self.is_playing = initial
    def cycle_play(self):
        if self.is_playing:
            socketio.emit('pause', broadcast=True)
            self.is_playing = False
    else:
            socketio.emit('play', broadcast=True)
            self.is_playing = True

You would then create an instance of this class, and pass the instance's cycle_play method to the same function you decorated your original function with. Because this behaviour is dynamic it's not appropriate to use a decorator on the method definition.

control = PlayControl(False)
socketio.on('request_play')(control.cycle_play)

To reduce the amount of program code you could even define a class that took the functions to call and the values to emit as arguments, generalising the concept further to make code more concise and with less boilerplate.

holdenweb
  • 33,305
  • 7
  • 57
  • 77
  • I have no experience with Flask+socketio but I doubt using a process-global state in any web application (the `control` object in the above example is still a global). – bruno desthuilliers Oct 05 '17 at 07:25
  • Correct. But you could (for example) have a list of controls, or a dictionary of controls you could look up by name. But then, you know that already. Perhaps I was too narrowly answering the OP's specific need. It might have helped if I hadn't made a blunderous formatting error (now fixed). – holdenweb Oct 05 '17 at 12:43
  • "Perhaps I was too literally answering the OP's specific need" : yes, my humble opinion... – bruno desthuilliers Oct 05 '17 at 12:44
  • And even a list or dict etc will break as soon as you have more than one single process serving the app (multiprocess setups being the norm). That's why we use databases (of any kind) for state persistence and sharing between processes. – bruno desthuilliers Oct 05 '17 at 12:52
  • Thanks. I hadn't read it carefully enough, and should have answered about session state. There is little need for your opinion to be humble on any Python topic. – holdenweb Oct 05 '17 at 12:54
  • Of course, now this answer has been accepted I can't delete it, and my idiocy is once again publicly revealed to all. Sigh. – holdenweb Oct 05 '17 at 12:56
0

My advice would be use a class and inside init method just use self.isPlaying = False. You can always refer to this variable from all the function in the class. for exemple:

class PLAYER(object):   
   def __init__(self,other parameters):
    self.isPlaying = False
    #your cod
   def cycle_play(self):
        #global isPlaying
        if self.isPlaying == True:
           socketio.emit('pause', broadcast=True)
           self.isPlaying = False
        else:
           socketio.emit('play', broadcast=True)
           self.isPlaying = True
Abhijit Pritam Dutta
  • 5,521
  • 2
  • 11
  • 17
  • This is fine, but you still have to arrange for the required events to call the method on a specific instance of that class. – holdenweb Oct 05 '17 at 12:45