开发者

Two python scripts behave differently when use different name for a user defined class

开发者 https://www.devze.com 2023-03-31 01:19 出处:网络
I am learning python. I encounter a strange problem. I create a class named \"test\" with several member functions and all is fine. However, when I substitute the class name to \"test1\", it goes wron

I am learning python. I encounter a strange problem. I create a class named "test" with several member functions and all is fine. However, when I substitute the class name to "test1", it goes wrong. I used vimdiff, diff and beyond compare to verify the two files, and see only the class name is different.

Here is the first script:

#!/usr/bin/python
#Filename: objvar.py

class test:
  '''Represents a person.'''
  population = 0
  def __init__(self, name):
    '''Initializes the person's data'''
    self.name = name
    print "(Initializing%s)"% self.name
    test.population += 1

  def __del__(self):
    '''I am dying'''
    print "%s says bye."% self.name
    test.population -= 1
    if test.population == 0:
      print "I am the last one."
    else:
      print "There are still %d people left."% test.population
  def sayHi(self):
    '''Greeting by the person.
    Really, that's all it does'''
    print "Hi, my name is %s."% self.name
  def howMany(self):
    '''Prints the current population.'''
    if test.population == 1:
      print "I am the only person here"
    else:
      print "We have%dpersons here."% test.population
ada = test("ada")
ada.sayHi()
ada.howMany()

yuwenlong = test("yuwenlong")
yuwenlong.sayHi()
yuwenlong.howMany()

ada.sayHi()
ada.howMany()

When it runs, the result is:

(Initializingada)
Hi, my name is ada.
I am the only person here
(Initializingyuwenlong)
Hi, my name is yuwenlong.
We have2persons here.
Hi, my name is ada.
We have2persons here.
yuwenlong says bye.
There are still 1 people left.
ada says bye.
I am the last one.

And here is the second script:

#!/usr/bin/python
#Filename: objvar.py

class test1:
  '''Represents a person.'''
  population = 0
  def __init__(self, name):
    '''Initializes the person's data'''
    self.name = name
    print "(Initializing%s)"% self.name
    test1.population += 1

  def __del__(self):
    '''I am dying'''
    print "%s says bye."% self.name
    test1.population -= 1
    if test1.population == 0:
      print "I am the last one."
    else:
      print "There are still %d people left."% test1.population
  def sayHi(self):
    '''Greeting by the person.
    Really, that's all it does'''
    print "Hi, my name is %s."% self.name
  def howMany(self):
    '''Prints the current population.'''
    if test1.population == 1:
      print "I am the only person here"
    else:
      print "We have%dpersons here."% test1.population
ada = test1("ada")
ada.sayHi()
ada.howMany()

yuwenlong = test1("yuwenlong")
yuwenlong.sayHi()
yuwenlong.howMany()

ada.sayHi()
ada.howMany()

When I run this one, the result is:

(Initializingada)
Hi, my name is ada.
I am the only person here
(Initializingyuwenlong)
Hi, my name is yuwenlong.
We have2persons here.
Hi, my nam开发者_C百科e is ada.
We have2persons here.
yuwenlong says bye.
Exception AttributeError: "'NoneType' object has no attribute 'population'" in <bound method test1.__del__ of <__main__.test1 instance at 0xb7ece0ec>> ignored
ada says bye.
Exception AttributeError: "'NoneType' object has no attribute 'population'" in <bound method test1.__del__ of <__main__.test1 instance at 0xb7ece0cc>> ignored

Would someone tell why the second script failed with just a different class name in the script text? My python version is Python 2.7.2.


This seems to have something to do with the order in which objects are destroyed at the end of the Python program. For some reason test gets deleted after ada and ywenlong, but test1 gets deleted before them. Why? I have no clue.

You can test out the arbitrariness of the deletion order with this program:

class test: pass
class test1: pass
class foo: pass
class huh: pass

class A:
    def __del__(self):
        print '''
            test:  %s
            test1: %s
            foo:   %s
            huh:   %s
            A:     %s
        ''' % (
            str(test), str(test1), str(foo), str(huh), str(A)
        )

a = A()

which prints (on my system):

        test:  __main__.test
        test1: None
        foo:   __main__.foo
        huh:   None
        A:     None

ie at the time of creation, test and foo are still around, but test1, huh and A have been deleted.

If you want to force deletion in a certain order you could

...
ada.sayHi()
ada.howMany()

del yuwenlong
del ada

Or just to get the class to stick around, store a reference to it:

class test1:

  def __init__(self, name):
    ...
    self.class_ref = test1

  def __del__(self):
    self.class_ref.population -= 1
    if self.class_ref == 0:
      print "I am the last one."
    else:
      print "There are still %d people left."% self.class_ref.population

But I would really love it if someone can explain

  1. What controls the deletion order?

  2. Why, given that you can still refer to test1 as ywenlong.__class__, does Python consider it appropriate to delete it?


@Owen has a good analysis of the problem: objects are being torn down in an arbitrary order, and test1 is torn down before test. I'll take a stab at answering Owen's follow-on questions:

1) "What controls the deletion order?" The order of tear-down is likely arbitrary, and probably dictated by dictionary order in the globals for the module.

This is borne out by adding a print globals() to the end of each script. The first prints:

{'__builtins__': <module '__builtin__' (built-in)>, 
 '__file__': 'so1.py', 
 'yuwenlong': <__main__.test instance at 0x0000000002367208>, 
 '__package__': None, 
 'ada': <__main__.test instance at 0x00000000023670C8>, 
 'test': <class __main__.testat 0x0000000001F06EB8>, 
 '__name__': '__main__', 
 '__doc__': None }

the second prints:

{'test1': <class __main__.test1 at 0x0000000001E46EB8>, 
 '__builtins__': <module '__builtin__' (built-in)>, 
 '__file__': 'so2.py', 
 'yuwenlong': <__main__.test1 instance at 0x0000000002307208>, 
 '__package__': None, 
 'ada': <__main__.test1 instance at 0x00000000023070C8>, 
 '__name__': '__main__', 
 '__doc__': None }

Notice that in the first, test comes after ada and yuwenlong, but in the second, test1 comes before them.

2) "Why, given that you can still refer to test1 as ywenlong.__class__, does Python consider it appropriate to delete it?" What's happening here isn't that the class test is being deleted. It's that the name test1 has been reassigned to None as part of tearing down the globals of the module. Remember, values are not deleted in Python, names are. The values go away when no names refer to them. In this case, the class was accessible as both test1 and as ywenlong.__class__. Then test1 was assigned to None, and the class was only accessible from one reference. The class isn't gone, just the reference through test1.

Two comments on the question have good advice: in a method, reference attributes through self, not through the class name, and don't use __del__ methods, they are difficult to make work properly.

0

精彩评论

暂无评论...
验证码 换一张
取 消