12

I just started learning Python and I ran into this problem. I want to set a variable from inside a function, but the variable is outside the function.

The function gets activated by a button. Then I want to get the value from that variable that I set when I press another button. The problem is that the value that I put inside a variable from inside the function doesn't stay. How would I solve this?

The code is underneath. currentMovie is the variable I try to change. When I press the button with the function update_text(), it prints out a random number like it is supposed to. But when I press the button that activates update_watched() it prints out 0. So I am assuming the variable never gets set.

import random
from tkinter import *

current_movie = 0

def update_text():
    current_movie = random.randint(0, 100)
    print(current_movie)

def update_watched():
    print(current_movie)

root = Tk()
root.title("MovieSelector9000")
root.geometry("900x600")
app = Frame(root)
app.grid()
canvas = Canvas(app, width = 300, height = 75)
canvas.pack(side = "left")
button1 = Button(canvas, text = "SetRandomMovie", command = update_text)
button2 = Button(canvas, text = "GetRandomMovie", command = update_watched)
button1.pack(anchor = NW, side = "left")
button2.pack(anchor = NW, side = "left")
root.mainloop()
1
  • Why don't you want to use return? Commented Oct 4, 2016 at 15:07

4 Answers 4

11

Use global to modify a variable outside of the function:

def update_text():
    global current_movie
    current_movie= random.randint(0, 100)
    print(current_movie)

However, don't use global. It's generally a code smell.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks, I could't understand why until I read this stackoverflow.com/questions/423379/…
What should I use instead of global?
@MikeOttink In this case it's a bit of a rough spot, as tkinter demands a function to update your variable. You can encapsulate your state in a class, but in this case solving it with a global isn't the worst thing. If you have a lot of state though you should generally encapsulate it.
To tell someone the only solution is to use global, and then give the advice not to use global isn't helpful for anyone seeking help on this problem. If you are going to categorically state "don't use global", at least give a hint at an alternative.
10

Here's a simple (python 2.x) example of how to 1 not use globals and 2 use a (simplistic) domain model class.

The point is: you should first design your domain model independently from your user interface, then write the user interface code calling on your domain model. In this case your UI is a Tkinter GUI, but the same domain model should be able to work with a command line UI, a web UI or whatever.

NB : for python 3.x, replace Tkinter with tkinter (lowercase) and you can get rid of the object base class for Model.

import random
from Tkinter import *


class Model(object):
    def __init__(self):
        self.current_movie = 0

    def update_current_movie(self):
        self.current_movie = random.randint(0, 100)
        print(self.current_movie)

    def update_watched(self):
        print(self.current_movie)

    def example_with_args(self, arg):
        print("ExampleWithArg({})".format(arg))


def main():
    model = Model()
    root = Tk()
    root.title("MovieSelector9000")
    root.geometry("900x600")
    app = Frame(root)
    app.grid()
    canvas = Canvas(app, width = 300, height = 75)
    canvas.pack(side = "left")
    button1 = Button(canvas, text = "SetRandomMovie", command=model.update_current_movie)
    button2 = Button(canvas, text = "GetRandomMovie", command=model.update_watched)
    button3 = Button(canvas, text = "ExampleWithArg", command=lambda: model.example_with_args("foo"))
    button1.pack(anchor = NW, side = "left")
    button2.pack(anchor = NW, side = "left")
    button3.pack(anchor = NW, side = "left")
    root.mainloop()

if __name__ == "__main__":
    main()

3 Comments

suppose, i need the value of updated currentMovie in my main function. How should I proceed then? Since I can't use return?
@bobthebuilder you just use model.currentMovie, why ?
My currentMovie variable doesn't get updated by the class functions for some reason. I wasn't finding any answers, so I had to ask a question on SO.
0

Here's a simple but dirty solution: use a mutable variable.

Instead of

currentMovie = 0

def UpdateText():
    currentMovie = random.randint(0, 100)
    print(currentMovie)

you can use a single-cell list for currentMovie and pass it as a (default) argument to UpdateText():

currentMovie = [0]

def UpdateText(cM=currentMovie): # The default value will 'bind' currentMovie to this argument
    cM[0] = random.randint(0, 100) # This will change the *contents* of the variable
    print(cM[0]) # I used a different name for the parameter to distinguish the two

UpdateText() # Calls UpdateText, updating the contents of currentMovie with a random number

Note that setting currentMovie itself (not its contents) with a new value—even with a new list—would cause UpdateText() to stop updating currentMovie unless the def block were run again.

currentMovie = [0]

def UpdateText(cM=currentMovie): # The default value will 'bind' currentMovie to this argument
    cM[0] = random.randint(0, 100) # This will change the *contents* of the list
    print(cM[0]) # I used a different name for the parameter to distinguish the two

currentMovie = 3 # UpdateText() will no longer affect this variable at all

# This will thus not throw an error, since it's modifying the 'old' currentMovie list:
UpdateText() # The contents of this list can also no longer be accessed

This is more of a handy trick if you're building something quick and dirty and don't want to build a class; I find that Python is great for such things, so I think that this is still worthwhile to share despite the other answers.

For more serious purposes, though, creating a class as in bruno's answer would almost certainly be better.

Comments

0

For me, the already mentioned answers did not work for two reasons.

  1. In case of an error, I need the variable for a deeper analysis of the data which led to the error.
  2. I'm using the function in a pandas.DataFrame.apply() to paste the usual output into a column of the existing DataFrame. Therefore the error information shall not be in the return statement.

Solution for me: Since I did not find a direct solution I decided to write the variable on disk:

with open('var.pickle', 'wb') as f:
    pickle.dump(var, f)

And then to import it where ever I need it:

with open('var.pickle', 'rb') as f:
    var = pickle.load(f)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.