natedogg
natedogg

Reputation: 95

Python OOP: functions from a dictionary do not affect class instance variables

I am new to Python but write programs for a hobby, so I have moderate knowledge of OOP and computer programming in general. I have started working on an simple animal simulator. In what might very well be a heathen move, I am trying to store all of the 'action functions' of the animal in a dictionary, so that each function is accessible by a string. For example, dict['SLEEP']() calls the sleep function.

I could find no examples of what I am trying to accomplish, and frankly am not sure how to intelligently describe my problem. See the bare-bones code below:

class Animal:

    def __init__(self):
        self.health = 100
        self.actions = {}  # dictionary of functions
        self.initializeAnimal()

    def initializeAnimal(self):
        self.actions['SLEEP'] = self.initializeSleep()  # add sleep function 

    def initializeSleep(self):
        RESTORED_HEALTH = 20
        # other constants

        def sleep(self):
            self.health += RESTORED_HEALTH
            # utilize other constants

        return sleep

Then, the animal handler would perform something along the following lines:

for animal in animalList:
    animal.actions['SLEEP']()

I'd of course like the animal's health to increase by 20 when the sleep function is called. Instead, nothing happens. After some research and experimenting, I see that the self passed to the sleep() function apparently refers to initializeSleep() rather than the animal class.

I am at somewhat of a loss as to how I would change the health of the animal when calling functions in this manner. Do I have to somehow make use of super-class calls?

edit: clarify syntax

Upvotes: 1

Views: 518

Answers (2)

jsbueno
jsbueno

Reputation: 110261

Python does some maneuvers so that functions defined in a class body actually behave as "methods" - and thus, get the "self" parameter added authomatically.

It is not hard to understand how that is done - and to emulate it for an explicit dictionary as you plan - but first, consider that you can retrieve a method name using a string, without resorting to storing them in dictionaries as you plan - you can simply do:

class Animal(object):
    ...
    def sleep(self, ...):
        ...

my_cow  = Animal()
function = getattr(my_cow, "sleep")
function (  ) 
# and of course, the two preceeding lines can be in a single expression:
getattr(a, "sleep")()

Now, let's see for the dicionary - since you defien the actual "sleep" function as a nested function, it will "see" the "self" variable as it exists in the invocation of initializeSleep() - which means what you are doing should just work - as soons as you fix the call to initializeSleep() by prefixing it with the self. , as in:

def initializeAnimal(self):
    self.actions['SLEEP'] = self.initializeSleep()  # add sleep function 

And remove the "self" parameter from the actual "sleep" function - it does not need it, as it will "see" the nonlocal self variable in the enclosing scope:

def initializeSleep(self):
    RESTORED_HEALTH = 20
    # other constants

    def sleep():
        self.health = RESTORED_HEALTH
        # utilize other constants

    return sleep

(The other constants defined inside the initializeSLeep will also be visible inside sleep as nonlocal variables)

Upvotes: 4

Serdalis
Serdalis

Reputation: 10489

You don't need to put the self attribute into the sleep function.
Its perfectly valid to do the following:

class Animal:
    def __init__(self):
        self.health = 100
        self.actions = {}  # dictionary of functions
        self.initializeAnimal()

    def initializeAnimal(self):
        self.actions['SLEEP'] = self.initializeSleep()  # add sleep function 

    def initializeSleep(self):
        RESTORED_HEALTH += 20
        # other constants

        def sleep():
            self.health += RESTORED_HEALTH
            # utilize other constants
        return sleep

a = Animal()
print(a.health)
a.actions['SLEEP']()
print(a.health)

Output:

100
120

As stated, you forogt the += in self.health = RESTORED_HEALTH.
You also missed the self in self.initializeSleep()

Upvotes: 1

Related Questions