7

I have the below piece of code that creates a note and adds to a notebook.

My question is more related to the global variable last_id. When I declare it as class variable i.e. inside Class Note, I get the following error but when I declare outside the class, my code works fine.

Here are my clarifications:

  1. Why does not it accept the class variable.
  2. Why do I need to defined last_id, when I declare it as a global variable inside my function?

Error:

C:\Python27\Basics\OOP\formytesting>python notebook.py
Traceback (most recent call last):
  File "notebook.py", line 38, in <module>
    firstnote = Note('This is my first memo','example')
  File "notebook.py", line 10, in __init__
    last_id += 1
NameError: global name 'last_id' is not defined

code.py

import datetime
last_id = 0
class Note:

    def __init__(self, memo, tags):
        self.memo = memo
        self.tags = tags
        self.creation_date = datetime.date.today()
        global last_id
        last_id += 1
        self.id = last_id

        #global last_id
        #last_id += 1
        #self.id = last_id

    def __str__(self):
        return 'Memo={0}, Tag={1}, id={2}'.format(self.memo, self.tags,self.id)


class NoteBook:
    def __init__(self):
        self.notes = []

    def add_note(self,memo,tags):
        self.notes.append(Note(memo,tags))

    def __iter__(self):         
        for note in self.notes:             
            yield note 



if __name__ == "__main__":
    firstnote = Note('This is my first memo','example')
    print(firstnote)
    Notes = NoteBook()
    print("Adding a new note object")
    Notes.add_note('Added thru notes','example-1')
    Notes.add_note('Added thru notes','example-2')
    for note in Notes.notes:
        print(note.memo,note.tags)

    for note in Notes:
        print(note)

    print("Adding a new note object----End")    
Santosh Kumar
  • 26,475
  • 20
  • 67
  • 118
user1050619
  • 19,822
  • 85
  • 237
  • 413

4 Answers4

10

When you write

global last_id

Inside your function, you are not creating a new global variable. What you are doing is saying "instead of creating a new local variable and associating it to the name last_id, instead associate that name to a pre-existing variable in the enclosing scope that has the name last_id"

If there is not already a variable called last_id, then global last_id doesn't refer to anything until you write to it. However if you write to it, it will be created in the global scope. For example:

>>> def x():
...     global X
...     X = 1
... 
>>> x()
# No error
>>> X
1 # X is now defined
>>> def y():
...     global Y
...     print Y
... 
>>> y()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in y
NameError: global name 'Y' is not defined
Gordon Bailey
  • 3,881
  • 20
  • 28
  • If I do `global last_id` and then `last_id=1`, it works fine. The phrase "if there is not already a variable `X` then `global X` doesn't refer to anything" is wrong. The variable can be defined later. – Mel Dec 03 '15 at 08:25
  • @tmoreau thanks you're right. I've updated my answer. – Gordon Bailey Dec 03 '15 at 15:04
  • Does the `global x` declaration need to be made in every function we use it in? – Pratik Khadloya Oct 06 '18 at 10:23
9

python is a bit more explicit about where attributes come from than other object oriented languages; you can have a class level counter, like so:

from itertools import count

class Foo(object):
    last_id = count()

    def __init__(self):
        self.id = self.last_id.next()

you must refer to last_id as self.last_id, so that python knows to look on the instance (and since it's not already there, the class).

SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
  • 1
    This is the only answer that is related to what has actually been asked. All other answers are either completely wrong or answer a different question. – Sven Marnach Aug 08 '12 at 21:15
  • @SvenMarnach - Just curious since my answer is close to this one - is there a problem with doing Foo.last_id vs. self.last_id? – dfb Aug 11 '12 at 01:09
  • @dfb: well, one shortcoming is that `Foo.last_id` would only be right if `type(self) == Foo`, but it could be the case that `type(self) != Foo and isinstance(self, Foo)`, if foo were subclassed, and the subclass provided its own `last_id`. slightly better might be to use `type(self).last_id`, but that seems a bit draconian to me. – SingleNegationElimination Aug 11 '12 at 01:38
2

The global statement doesn't bind a name, it merely tells the compiler that the variable should be rebound in global scope when it happens. You must still perform the initial bind yourself.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
0

Python uses the variable's inner-most scope, so this

def __init__(self, memo, tags):
     ....
    global last_id
    last_id += 1

will work but this

def __init__(self, memo, tags):
     ....
    last_id += 1

will not.

You could rewrite it as

class Note:
    last_id = 0;
    def __init__(self, memo, tags):
        self.memo = memo
        self.tags = tags
        self.creation_date = datetime.date.today()
        Note.last_id += 1
        self.id = last_id
dfb
  • 13,133
  • 2
  • 31
  • 52