1

I have a problem with my understanding of python global variables and their correct usage. The first bit of code works fine, the second bit throws the exceptions "NameError: global name 'chosen' is not defined". I am sure I am missing something simple here, but I can not see what it is.

I want to associate a Listener with each of many event generators and then use the getChoices method to get a dictionary of the choice made be each generator.

#Working code
class Listener1():
  chosen = "0"
  def __init__(self, choice):
    self.choice = choice

  def actionPerformed(self, event):            
    global chosen
    chosen = self.choice

  @staticmethod
  def getChoices():
    return chosen

e1 = Listener1('evt1')
e1.actionPerformed('abc')
print Listener1.getChoices()

Failing code

class Listener2():  
  chosen2 = {'a':-1}
  def __init__(self, choice):
    self.choice = choice
    global chosen2
    chosen2[self.choice] = 'unset'

  def actionPerformed(self, event):
    val = event
    global chosen2
    chosen2[self.choice] = val

  @staticmethod
  def getChoices():
    return chosen2

e2 = Listener2('evt2')
e2.actionPerformed('def')
e3 = Listener2('evt3')
e3.actionPerformed('ghi')
print Listener2.getChoices()

Footnote: the Listener2 class works correctly if I move the first reference to the global variable chosen2 to the line before the class definition instead of the line after.

Thanks to the answers below, code rewritten as:

class Listener3():
  chosen3 = {}
  def __init__(self, choice):
    self.choice = choice
    if choice is not None:
        self.chosen3[self.choice] = 'unset'

  def actionPerformed(self, event):
    val = event
    self.chosen3[self.choice] = val

  def getChoices(self):
    return self.chosen3

e1 = Listener3('evt1')
e1.actionPerformed('abc')
e2 = Listener3('evt2')
e2.actionPerformed('def')
e3 = Listener3('evt3')

print Listener3(None).getChoices()
{'evt1': 'abc', 'evt2': 'def', 'evt3':'unset'}

And apart from being much simpler, is now working perfectly.

Paul Smith
  • 454
  • 6
  • 11
  • Question answered. Thank you to Martijn, Matthew and heyden for not being rude to me in missing this most obvious problem. – Paul Smith Sep 12 '12 at 13:49

3 Answers3

2

Neither global does what you think it does. In both cases chosen and chosen2 are class variables, not globals.

But when you declared chosen as a global and assigned to it, python happily created a global variable (separate from Listener1.chosen) and stored your value.

But for chosen2 you are not assigning anything to it; you are trying to treat it as a dict instead, but you didn't create the variable by assignment so that fails.

You want to use self.chosen2 instead; as a class variable, it'll be available to all instances of Listener2. You can also use Listener2.chosen2. For chosen, you can use Listener1.chosen to refer to it. The global keywords can be dropped altogether.

In any case, declaring a variable as global does not mean outside of my current scope. It means at module scope instead, so always outside of your functions, classes, and method definitions.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
0

You can recreate the same error:

global chosen2 #you're not creating chosen2, just saying you're about to create it, and when you do - it should be a global variable
chosen2[2]=2 #chosen2 has yet to be defined so how can you set anything?

You're trying to set a property of an as yet undefined object two, first you must say whether you want it to be a list or dictionary (or otherwise).

global chosen2
chosen2={}
chosen2[2]=2

As mentioned I don't think you want this to be global (as you want be able to create class instances with different values of chosen2!

Andy Hayden
  • 359,921
  • 101
  • 625
  • 535
0

You don't need to use global here. In your examples, chosen and chosen2 are class attributes. Instead of:

global chosen
chosen = self.choice

You'd just write:

self.chosen = self.choice

When you define chosen2 before the class, you're actually defining it in the scope that global refers to. When you define it within the class scope, it's a class attribute that is shared by all instances.

Update: as pointed out, straight assignment to the class attribute isn't what you want, as it'll create a new attribute on the instance which masks it on the class. But in your Listener2 example, this will be fine:

def actionPerformed(self, event):
    self.chosen[self.choice] = event

Here, you're not overwriting .chosen, but adding an item to the dictionary it refers to, so the class will keep a record of the most recent event for each listener.

Matthew Trevor
  • 14,354
  • 6
  • 37
  • 50