Changing class name causes process to 'hang'

Terry Reedy tjreedy at udel.edu
Sun Mar 13 17:36:40 EDT 2011


On 3/13/2011 3:17 PM, Tim Johnson wrote:
> * Tim Johnson<tim at johnsons-web.com>  [110313 08:27]:
>>    One other thing I just realized:
>>    The process stops inside of a function call to another object
>>    method, if that method call is removed, the process teminates.
>>    :) I may have a solution later today, and will relay it to you if
>>    found. Must have coffee first.
>    I've had coffee, and also I've eaten a bit of crow. If I had no
>    dignity to spare I might have dropped this thread after the last
>    post, but I owe it to those who might come after me to explain
>    what happened. If I have any excuse, it is that I wrote this code
>    when I had about a month of python experience.

One measure of people is how big a mistake they are willing to make -- 
in public. I learned to always test or disclaim as 'not tested' posted 
code by posting undisclaimed bad code -- more than once.

>    So here we go...
>    The cleanup part is to write a logfile. The logfile is written by
>    the write() method of a log object. The method was coded to accept
>    any number of data types and execute based on the type from an
>    if/elif/else code block. The cgi object was passed to the method.
>    The original code follows:
>    ## code begins
>    		if type(args) == type({}): ## it's a dictionary
> 			args['time_date_stamp'] = '%s%d' % (std.local_time(),std.randomize(8))
> 			keys = args.keys()
> 			keys.sort()
> 			for key in keys:
> 				outfile.write('\t%s: %s\n' % (key,args[key]))
> 		elif type(args) == type(''):
> 			outfile.write('%s\n%s\n' % (std.local_time(),args))
> 		elif std.IsCgiObj(args):   ## dump the cgi object
> 			dump = args.getEnv('time_date_stamp=%s' % (std.local_time()))
> 			for line in dump:
> 				outfile.write('  %s\n' % line)
> 		else : ## default = it's a list
> 			if args:
> 				outfile.write('time_date_stamp=%s\n' % (std.local_time()))
> 				for arg in args:
> 					outfile.write('  %s\n' % arg)
>    ## /code ends
> I did two obvious things wrong here:
> First of all, std.IsCgiObj() returned false when I changed
> the class name because std.IsCgiObj() tested for an explicit
> match of 'cgitools' with the objects __class__.__name__ member.

Your fundamental problem is that you changed the api of your module. 
When you do that, you have to review all code that uses that api -- or 
that depends on it. But yes, a module can use a string based on the api 
without actually importing the module. Bad idea I think. Better to 
import the module and make the dependency overt, so the failure is also.
The test should be (and should have been)

    elif isinstance(args, cgi.cgitools):

This would have and will fail with a loud AttributeError when cgitools 
is renamed.

>
> Secondly, and worse, the default of the test block was an assumption
> and I did not test the assumption. Bad, bad, very bad!
> Therefore my code attempted to process the object as a list and down
> the Rabit Hole we went. And I ended up with some *really* big
> logfiles :).

Thank you for confirming that I was basically right that *somewhere* 
there had to be a test for the name 'cgitools' whose failure lead to a loop.

> Following is a tentative revision:
>    ## code begins
> 		elif 'instance' in (str(type(args))):   ## it's an object

*Everything* is an object and therefore an instance of some class. What 
you are testing is whether the object is an instance of a classic class 
defined by a Python 2.x (classic) class statement. Rather fragile;-), as 
I just hinted.

class C(): pass

C()

# 2.7 prints
<__main__.C instance at 0x00C41FD0>
# 3.2 prints
<__main__.C object at 0x00F0A5F0>

class C(object): pass

C()
# 2.7 also prints
<__main__.C object at 0x00C71130>


> 			if hasattr(args,'getEnv'): ## test for method
> 				dump = args.getEnv('time_date_stamp=%s' % (std.local_time()))
> 				for line in dump:
> 					outfile.write('  %s\n' % line)
> 			else :
> 				erh.Report("object passed to logger.write() must have a `getEnv()' method" )
> 		else : ## it's a list

  just test elif isinstance(args, list): ...

and put error message in separate else; clause

> 			if type(args) != []:  ## make no assumptions
> 				erh.Report('List expected in default condition of logger.write()')
> 			if args:
> 				outfile.write('time_date_stamp=%s\n' % (std.local_time()))
> 				for arg in args:
> 					outfile.write('  %s\n' % arg)
>    ## /code ends
 >    ## erh.Report() writes a messages and aborts process.

Consider upgrading to 2.7 if you have not and using the logging module.

> Of course, I could have problems with an object with a
> malfunctioning getEnv() method, so I'll have to chew that one over
> for a while.
> I appreciate Terry's help. I'd welcome any other comments. I'm
> also researching the use of __class__.__name__. One of my questions
> is: can the implementation of an internal like __class__.__name__
> change in the future?

Double double underscore names are 'special' rather than necessarily 
'private'. These two are somewhat documented (3.2 Reference, 3.1, 
"__class__ is the instance’s class", "__name__ is the class name"). So 
they will not change or disappear without notice. I expect them to be 
pretty stable.

On the other hand, they are mostly intended for internal use by type(ob) 
(for every ob), str/repr(ob) (for many obs), tracebacks, and other 
messages. Indeed, one 'should' use type(ob) rather than ob.__class__. 
However, custom messages may require direct use of .__name__. As you 
discovered, using it for program logic has problems.

-- 
Terry Jan Reedy





More information about the Python-list mailing list