Class destructor -- strange behaviour

Gabriel Genellina gagsl-py2 at yahoo.com.ar
Fri Dec 7 01:14:23 EST 2007


En Thu, 06 Dec 2007 18:51:08 -0300, Spes <Vlastimil.Holer at gmail.com>  
escribió:

> I have this simple code:
> | #!/usr/bin/python
> | import codecs
> | import re
> | from copy import deepcopy
> |
> | class MyClass(object):
> |   def __del__(self):
> |     deepcopy(1)
> |
> | x=MyClass()
>
> but I get an error:
> | Exception exceptions.TypeError: "'NoneType' object is not callable"
> in <bound method MyClass.__del__ of <__main__.MyClass object at
> 0x6fcf0>> ignored

Good question!
First: what does the message mean? Somewhere inside __del__, you are  
trying to call an object, and that object is None. The only possible cause  
is deepcopy being None, and you can confirm that easily: adding a 'print  
deepcopy' just above the call, shows that deepcopy is actually None inside  
__del__.

This is related to the Python finalization process. At some stage, Python  
clears the __main__ module's namespace: not by removing the names, but  
assigning None to them, and finally removing __main__ from sys.modules.  
(The same thing is done, in turn, for all other modules, ending with sys  
and __builtin__). That means that you can't trust any globals inside a  
destructor (see the second big warning at  
http://docs.python.org/ref/customization.html).
You can avoid this problem (partially) storing a reference to deepcopy  
into the *local* namespace:

	def __del__(self,
	               deepcopy=deepcopy):
	  ...
(this works as long as the deepcopy function itself does not reference  
other globals that might not be still available)

> The problem disappears if I do anything of this:
> 1. change
>      - from copy import deepcopy
>      + import copy
>     and call directly copy.deepcopy(1)
>
> or
> 2. don't store object to variable `x'
>
> or
> 3. don't import module `re'

Back to the finalization process, the names in the __main__ module  
namespace are set to None IN THE ORDER IN WHICH THEY ARE ENCOUNTERED (in  
fact, this is done in two stages: first names starting with '_', then all  
remaining names, but excluding '__builtins__'). So, if "deepcopy" (as a  
dictionary key) comes before "x" (the variable name) when you iterate the  
module's dict, deepcopy will be set to None before x, and when x.__del__  
is called it will fail.

Now try your examples again and print globals.keys() for each variant, and  
notice the name ordering. If you can arrange things so 'x' (or whatever  
variable name you choose) comes before than 'deepcopy' in globals.keys(),  
you won't get any error.
Note that, for example, types are irrelevant: you can replace 'import re'  
with 're=0' and get the same results.

> The first solution is OK, but I would like to know why it behaves so
> strange. We have tested on:

I hope my explanation is understandable. If you want to know the gory  
details, see Py_Finalize in pythonrun.c, PyImport_Cleanup in import.c and  
_PyModule_Clear in moduleobject.c

> - Mac OS X Tiger for PPC
> Python 2.3.5 (#1, Mar 20 2005, 20:38:20)
> [GCC 3.3 20030304 (Apple Computer, Inc. build 1809)] on darwin
>
> - Linux 64bit and 32bit
> Python 2.4.4 (#1, Oct 30 2007, 14:31:50)
> [GCC 4.1.2 (Gentoo 4.1.2 p1.0.2)] on linux2
> Python 2.5 (r25:51908, Jan 12 2007, 13:57:15)
> [GCC 4.0.2 20051125 (Red Hat 4.0.2-8)] on linux2

Mmm, you got the same results on 64 bit too? I would have expected that,  
having a 64 bits hash value, key ordering inside a dictionary would be  
different. Perhaps it's just a coincidence.

-- 
Gabriel Genellina




More information about the Python-list mailing list