Dos scripts de Python se comportan de manera diferente cuando usan un nombre diferente para una clase definida por el usuario

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 name 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.

preguntado el 28 de agosto de 11 a las 03:08

You should not use the name of the class to access class variables. -

Oh, but could you tell me why my first script works well? Thank you very much. -

Also, you should never rely on __del__. It's not safe... Just write your code in a Pythonic way and be happy -

@JBernardo why shouldn't you use the class name? -

@Lucas because subclasses will not be able to change the behavior of that. Using super o incluso type(self) es mejor -

2 Respuestas

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 y foo are still around, but test1, huh y 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?

Respondido 28 ago 11, 07:08

Thank you very much, it's very helpful for me. - user916065

@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 viene después ada y 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 y como ywenlong.__class__. Luego 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.

Respondido 28 ago 11, 07:08

Thank you very much, it's very helpful to me. - user916065

Than you for that explanation. Makes sense now :) - Owen

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.