Two python scripts behave differently when use different name for a user defined class
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
What controls the deletion order?
Why, given that you can still refer to
test1
asywenlong.__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.
精彩评论