0

I have just started to learn about classes and objects in Python and I came to following problem

It doesnt seem to work with variable n in methods I defined in some cases:

for example:

def play(self):
    if n < 4:
        n += 1
        self.mood = moods[n]
    else:
        self.mood = self.mood

this throws and error that n is not defined on line "if n < 4:" but if I erase this part "n += 1" error does not appear. But even after that it doesnt seem to update variable n if I use it in method:

def new_mood(self):
    n = random.randint(0,2)
    self.mood = moods[n]    

Am I missing some fundamental knowledge here?

import random

moods = ['terrible', 
         'bad', 
         'neutral', 
         'good', 
         'great']

n = random.randint(0,2)

class animals:

    def __init__(self, species, name, mood):
        self.species = species
        self.name = name
        self.mood = mood

    def default_mood(self):
        self.mood = moods[2]

    def new_mood(self):
        n = random.randint(0,2)
        self.mood = moods[n]    

    def play(self):
        if n < 4:
            n += 1
            self.mood = moods[n]
        else:
            self.mood = self.mood


Max = animals('Dog', 'Max', moods[n])
Princess = animals('Cat', 'Princess', moods[n])


print(Max.name + ' mood is ' + Max.mood)
print(Princess.name + ' mood is ' + Princess.mood)

Max.new_mood()
Max.play()

Princess.play()


print(Max.name + ' mood is ' + Max.mood)
print(Princess.name + ' mood is ' + Princess.mood)

print(Max.mood)

print(Max.name + ' mood is ' + Max.mood)
print(Princess.name + ' mood is ' + Princess.mood)

1 Answers1

0

TLDR: This is not caused by classes but by the way scoping works in Python. Any assignment to a name makes that name a local variable, shadowing any global ones of the same name.

Use global or nonlocal to explicitly refer to names from global or containing scope. Use class attributes to refer to names from class scope.


Consider this minimal example without a class:

>>> n = 5
...
>>> def foo():
...    if n < 10:
...        n += 1
...
>>> foo()
UnboundLocalError: local variable 'n' referenced before assignment

Notice how the error says local variable? The n inside foo is not the global n! Since the local n is not initialised until its assignment, it cannot be used in the comparison beforehand. You would get, and probably also expect, the same error if the global n did not exist.

Whenever a name is assigned to in a scope, that automatically makes this name local to that scope. Note that this affects the entire scope - including occurrences before the assignment. If you do only an assignment, that only changes the local name - which is thrown away at the end of the scope.

If you want to modify a name in an outer scope, you have to tell Python. The global and nonlocal keywords exist for this:

>>> def foo():
...    global n     # n refers to the global name n for the entire scope
...    if n < 10:   # works, we compare against the global n
...        n += 1   # modifies the global n, no introduction of local n

A related but slightly different use cases are variables specific to a class. For example, n may be a feature of all animals without affecting, say, all humans.

Such class attributes are defined in the body of the class. You can refer to them by the class' name, similar to how you would fetch a method:

class Animals:
    n = 0

    def new_mood(self):
        # we want the 'n' of Animals
        Animals.n = random.randint(0,2)
        self.mood = moods[Animals.n]  

There are several ways to use class attributes, depending on how one wants to handle modifying and subclassing.

MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119