Programming Idiomatic Code

Bruno Desthuilliers bruno.42.desthuilliers at wtf.websiteburo.oops.com
Tue Jul 3 04:23:53 EDT 2007


Nathan Harmston a écrit :
> Hi,
> 
> I m sorry but I m bored at work (and no ones looking so I can write
> some Python) and following a job advertisement post,I decided to write
> the code to do its for the one entitled Ninjas or something like that.
> I was wondering what could be done to my following code to make it
> more idiomatic...or whether it was idiomatic and to be honest what
> idiomatic really means. All comments greatly appreciated and welcomed.
> 
> Thanks in advance
> 
> Nathan
> 
> import urllib2,sys
> from elementtree.ElementTree import parse
> 
> base_url = "http://api.etsy.com/feeds/xml_user_details.php?id="

Using a module global for this kind of data is usually a bad idea 
(except eventually for run-once throw-away scripts, and even then...)

> def read_id_file(filename):
>    """ reads a file and generates a list of ids from it"""
>    ids = [ ]
>    try:
>     id_file = open(filename, "r")
>     for l in id_file:
>            ids.append( l.strip("\n") )
>     id_file.close()

Python has other idioms for this (list comprehensions etc).


>    except e:
>        print e
>        os._exit(99)

This is a very bad way to handle exceptions.
1/ you use a catch-all except clause, when you should be specific about 
what kind of exceptions you are willing to handle here
2/ names starting with an underscore are not part of the API. You should 
not use them unless you have a *very* compelling reason to do so.
3/ stdout is for normal outputs. Error messages and such should go to stderr
3/ anyway, if it's for printing the exception then exit, you'd better 
not handle anything - you'd have a similar result, but with a full 
traceback.

>    return ids

def read_id_file(filename):
   try:
     f = open(filename)
   except IOError, e:
     print >> sys.stderr, \
       "failed to open %s for reading : %s" \
        % (filename, e)
     return None
   else:
     ids = [l.strip() for l in f]
     f.close()
     return ids


> def generate_count(id_list):
>    """ takes a list of ids and returns a dictionary of cities with
> associated counts"""
>    city_count = {}
>    for i in id_list:
>        url = "%s%s" %(base_url,i)

base_url should be passed in as an argument.

>     req = urllib2.Request(url)

I assume there's a problem with indentation here...

>     try:
>            xml = parse(urllib2.urlopen(url)).getroot()
>            city  = xml.findtext('user/city')
>     except e:
>            print e.reason
>            os._exit(99)

cf above

>     try:
>            city_count[city] += 1
>        except:

should be 'except KeyError:'

>            city_count[city] = 1

This idiom is appropriate if you expect few exceptions - that is, if the 
normal case is that city_count.has_key(city) == True. Else, you'd better 
use an explicit test, a defaultdict, etc. The idiomatic expliciti test 
would be:

       if city not in city_count:
         city_count['city'] = 1
       else:
          city_count[city] += 1


>    return city_count
> 
> if __name__=='__main__':
>    if len(sys.argv) is 1:

'is' is the identity operator. *Never* use it for numeric equality test. 
This should be:
      if len(sys.argv) == 1:

>        id_list = [ 42346, 77290, 729 ]
>    else:
>        try: id_list = read_id_file(sys.argv[1])
>        except e: print e

cf above about exception handling.

And FWIW, here, I would have used a try/except :

    try:
      filename = sys.argv[1]
    except IndexError:
      id_list = [1, 2, 3]
      print >> sys.stderr, \
        "no id file passed, using default id_list %" % id_list
    else:
      print >> sys.stderr, \
        "reading id_list from id_file %s" % filename
      id_list = read_if_file(filename)

    if not id_list:
      sys.exit(1)

>    for k, v in generate_count(id_list).items():
>        print "%s: %i" %(k, v)

There's a simpler way:

      for item in generate_count(id_list).iteritems():
          print "%s: %i" % item



More information about the Python-list mailing list