Richard
Richard

Reputation: 123

How to avoid using a global variable?

Getting back into programming after a 20 years hiatus. Been reading that the use of global variables in python is a sign of bad design, but can't figure out a better way of doing it.

Below is a small program that utilizes a global variable 'paused' to determine the state of the music player. This variable is utilized by a couple of functions.

Is there a better way of doing this without utilizing a global variable?

# Global variable to access from multiple functions
paused = False


def play_music():

    global paused

    if not paused:

        try:
            mixer.music.load(filename)
            mixer.music.play()
            statusBar['text'] = 'Playing Music - ' + os.path.basename(filename)
        except:
            tkinter.messagebox.showerror('File not found',
                                         'Melody could not find the file.')
    else:
        mixer.music.unpause()
        paused = False
        statusBar['text'] = 'Playing Music - ' + os.path.basename(filename)


def stop_music():
    mixer.music.stop()
    statusBar['text'] = 'Music stopped'


def pause_music():

    global paused

    if not paused:
        mixer.music.pause()
        paused = True
        statusBar['text'] = 'Music paused'

    else:
        play_music()

Upvotes: 4

Views: 1907

Answers (4)

Richard
Richard

Reputation: 123

In case anyone else is interested, below is the improved code where a Player class was created to encapsulate the pause variable:

import os
from tkinter import *
from tkinter import filedialog
import tkinter.messagebox
from pygame import mixer

# Global variable to access from multiple functions
# paused = False


class Player:

    def __init__(self):
        self.paused = False
        self.filename = None

    def play_music(self):

        if not self.paused:

            try:
                mixer.music.load(self.filename)
                mixer.music.play()
                statusBar['text'] = 'Playing Music - ' + os.path.basename(self.filename)
            except FileNotFoundError:
                tkinter.messagebox.showerror('File not found',
                                             'Melody could not find the file. Please choose a music file to play')
        else:
            mixer.music.unpause()
            self.paused = False
            statusBar['text'] = 'Playing Music - ' + os.path.basename(self.filename)

    @staticmethod
    def stop_music():
        mixer.music.stop()
        statusBar['text'] = 'Music stopped'

    def pause_music(self):

        if not self.paused:
            mixer.music.pause()
            self.paused = True
            statusBar['text'] = 'Music paused'

        else:
            self.play_music()

    def rewind_music(self):
        self.play_music()
        statusBar['text'] = 'Music rewound'

    @staticmethod
    def set_volume(val):
        # val is set automatically by the any tkinter widget

        volume = int(val)/100   # mixer only takes value between 0 and 1
        mixer.music.set_volume(volume)

    # Create about us Message Box
    @staticmethod
    def about_us():

        tkinter.messagebox.showinfo('About Melody', 'This is a music player built using python and tkinter')

    def browse_file(self):

        self.filename = filedialog.askopenfilename()
        print(self.filename)


# Create main window
root = Tk()

# Create window frames
middle_frame = Frame(root)
bottom_frame = Frame(root)

# Create Menu
menu_bar = Menu(root)
root.config(menu=menu_bar)

# Create Player object
player = Player()

subMenu = Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="File", menu=subMenu)
subMenu.add_command(label="Open", command=player.browse_file)
subMenu.add_command(label="Exit",  command=root.destroy)

# it appears we can re-use subMenu variable and re-assign it
subMenu = Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="Help", menu=subMenu)
subMenu.add_command(label="About Us", command=player.about_us)


# Initialise Mixer
mixer.init()

# Create and set the main window
root.title("Melody")
root.wm_iconbitmap(r'favicon.ico')
# root.geometry('300x300')


# Create and arrange widgets
text = Label(root, text="Lets make some noise!")
text.pack(pady=10)
middle_frame.pack(pady=30, padx=30)   # Place the middle and bottom frame below this text
bottom_frame.pack()

playPhoto = PhotoImage(file='play-button.png')
playBtn = Button(middle_frame, image=playPhoto, command=player.play_music)
playBtn.grid(row=0, column=0, padx=10)

stopPhoto = PhotoImage(file='stop-button.png')
stopBtn = Button(middle_frame, image=stopPhoto, command=player.stop_music)
stopBtn.grid(row=0, column=1, padx=10)

pausePhoto = PhotoImage(file='pause-button.png')
pauseBtn = Button(middle_frame, image=pausePhoto, command=player.pause_music)
pauseBtn.grid(row=0, column=2, padx=10)

rewindPhoto = PhotoImage(file='rewind-button.png')
rewindBtn = Button(bottom_frame, image=rewindPhoto, command=player.rewind_music)
rewindBtn.grid(row=0, column=0, padx=20)

# Create and set volume slider
scale = Scale(bottom_frame, from_=0, to=100, orient=HORIZONTAL, command=player.set_volume)
scale.set(70)  # set default slider and and volume
player.set_volume(70)
scale.grid(row=0, column=1, padx=10)

statusBar = Label(root, text='Welcome to Melody', relief=SUNKEN, anchor=W)
statusBar.pack(side=BOTTOM, fill=X)

# Keep main window displayed
root.mainloop()

Upvotes: 1

kederrac
kederrac

Reputation: 17322

if you do not want to use classes, you could pass a settings dictionary that has a pause key, it will be mutated in all over your functions

def play_music(settings):
    # some extra code
    settings['pause'] = False

def stop_music(settings)
    # some extra code
    settings['pause'] = None

def pause_music(settings):
   # some extra code
   settings['pause'] = True

def main():
    settings = {'pause': None}
    play_music(settings)
    .....

Upvotes: 0

damdamo
damdamo

Reputation: 353

Indeed, using global variable isn't recommended. You can have side effects that leads your program to have an unexpected behavior.

You have the possibility above (using class), but another solution is just to pass your variable as a parameter of your function.

def play_music(paused):
  ...
def stop_music(paused):
  ...
def pause_music(paused):
  ...

Upvotes: 0

Óscar López
Óscar López

Reputation: 236124

You could put all your functions inside a class, and make the "global" variable an attribute. In that way you can share it between methods:

class Player(object):
    def __init__(self):
        self.paused = False
    def play_music(self):
        if not self.paused:
            # and so on
    def pause_music(self):
        if not self.paused:
            # etc.

Upvotes: 9

Related Questions