[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] Please explain what I've done wrong.



[ I'm pretty sure this is off topic for this list, but the response you've had
   might risk you writing code that's even harder for you to see what's
   happening ]

On Saturday 17 September 2005 20:00, Jason wrote:
> I've looked through the code but can't find anything obvious.

It's because there isn't a problem with your code, per se, it's more to do 
with python shutdown.

If you put your code, including the test cases, inside a script and run it, 
the following happens:
   * The class is created
   * The instances are created
   * We hit the end of the script, so python starts to shutdown
   * The class and instances are __del__eted at shutdown, *in no particular
     order*
   * BANG!

If deletion is "forced" to happen in the following order - instances, followed 
by class - by adding the following at the end of your code:

Jason = None # invalidate Jason, allow to be deleted
Sophie = None # invalidate Sophie, allow to be deleted
import time
t = time.time()
while time.time() - t < 1:
   pass

Then the code works as you expect.

The problem you're seeing is the deletion appears to happen in the following 
order - at least on my machine mirroring your error:
   * Jason deleted
   * class deleted
   * Sophie deleted - BANG! (due to trying to reference something that is no
     longer really valid)

Clearly this means that by the time Sophie is deleted "Person" no longer 
really exists, and hence can't really be used for anything. 

(Actually I suspect the actual nitty-gritties under the hood are a little more 
complex, but the above APPEARS to be resulting behaviour. I'd have to look 
inside the python code to know for certain)

If you change your tests to something slightly more idiomatic, such as:

def main():
    Jason=Person("Jason")
    Jason.sayHi()
    Jason.howMany()

    Sophie=Person("Sophie")
    Sophie.sayHi()
    Sophie.howMany()

if __name__ == "__main__":
     main()

Then you find you get the right behaviour. The reason is because the locals in 
main() are no longer valid the instant the function call exits.

I'd advise BTW against trying to access & update a class variable via the 
alternate suggestion of self.population since you're likely to run into all 
sorts of bizarre issues that way.

A better alternative to self.population is to do self.__class__.population  - 
which would update the class that self belongs to.

For example if I modify your example to also have a Frank, who is an employee:

class Frank(Person):
    population = 0

And change all occurances of Person.population to self.__class__.population 
and all occurances of the string "Person" with self.__class__.__name__, then 
you get the following behaviour:

(Initialising Jason)
Hi, my name is Jason
I am on the only Person  here.
(Initialising Sophie)
Hi, my name is Sophie
We have 2 Persons here.
(Initialising Jason)
Hi, my name is Frank
I am on the only Employee  here.
Sophie says bye.
There are still 1 people left.
Jason says bye.
I am the last  Person
Jason says bye.
I am the last  Employee

When the following test harness runs:
def main():
    Jason=Person("Jason")
    Jason.sayHi()
    Jason.howMany()

    Sophie=Person("Sophie")
    Sophie.sayHi()
    Sophie.howMany()

    Frank=Employee("Frank")
    Frank.sayHi()
    Frank.howMany()

(Full version of this one included at end)

Person.population would hardcode the class to specifically only update the
Person population, which is a valid design decision.

self.__class__.population means that you're keeping track of how many of each
kind of thing exists (number of employees for example vs other people). Again, 
another equally valid design decision. (Though in the example below this 
would imply that Employees aren't people, which isn't an idea I'd like 
employers to take on :-)

self.population can mean different things depending on whether you're
reading or writing the value, which is not an ideal scenario :-) (If you
change all occurances of self.__class__.population below to self.population,
you'll see what I mean...)

I suppose the bottom line here (if the rest of this email has gone over your 
head) is that your code was fine, BUT you may want to try running it 
differently :)

If you really want to see why self.population is a bad idea, take the example 
below and use:
class Employee(Person):
   pass

Instead of :
class Employee(Person):
    population = 0

Which again gives you another behaviour (due to, again, how values for names 
are searched for and updated).

Finally, I suspect the best place for this question is actually the 
python-tutor list - http://mail.python.org/mailman/listinfo/tutor

Best Regards,


Michael.
-----
#!/usr/bin/python


class Person:
     population=0

     def __init__(self,name):
         self.name=name
         print '(Initialising %s)' % self.name
         self.__class__.population += 1

     def __del__(self):
         print "%s says bye." % self.name
         self.__class__.population -= 1

         if self.__class__.population == 0:
             print "I am the last ",self.__class__.__name__
         else:
             print "There are still %d people left." % 
self.__class__.population

     def sayHi(self):
         '''Greeting by the person.

         That's all it does.'''
         print "Hi, my name is %s" % self.name

     def howMany(self):
         if self.__class__.population==1:
             print "I am on the only", self.__class__.__name__, " here."
         else:
             print ("We have %d "+self.__class__.__name__+"s here.") % 
self.__class__.population

class Employee(Person):
    population = 0

def main():
    Jason=Person("Jason")
    Jason.sayHi()
    Jason.howMany()

    Sophie=Person("Sophie")
    Sophie.sayHi()
    Sophie.howMany()

    Frank=Employee("Frank")
    Frank.sayHi()
    Frank.howMany()

if __name__ == "__main__":
     main()