switch

Asun Friere afriere at yahoo.co.uk
Thu Dec 10 07:29:37 EST 2009


On Dec 10, 6:57 am, Tim Chase <python.l... at tim.thechases.com> wrote:
> Carl Banks wrote:
> > What if the object is a string you just read from a file?
>
> > How do you dispatch using polymorphism in that case?
>
> This is where I most miss a switch/case statement in Python...I
> do lots of text-file processing (cellular provider data), so I
> have lots of code (for each provider's individual format) that
> looks like
>
>    phones = {}
>    for row in csv.DictReader(file('data.txt', 'rb')):
>      phonenumber = row['phonenumber']
>      if phonenumber not in phones:
>        phones[phonenumber] = Phone(phonenumber)
>      phone = phones[phonenumber]
>      rectype = rectype
>      if rectype == '01':
>        phone.international += Decimal(row['internationalcost'])
>      elif rectype == '02':
>        phone.text_messaging += (
>          int(row['textmessages sent']) +
>          int(row['pages received']) +
>          int(row['textmessages sent']) +
>          int(row['pages received'])
>      elif rectype == ...
>         ...
>      else:
>        raise WhatTheHeckIsThis()
>

Great example Tim.   This is something that many of us must be dealing
with on a daily basis.  The problem has enough details (bar one), to
allow an answer and not so detailed as to be confusing.  And for me
it's a particularly good example, because your need accommodate
mulitple provider formats makes me feel right at home.

> which would nicely change into something like
>
>    switch row['recordtype']:
>      case '01':
>        phone.international += Decimal(row['internationalcost'])
>        // optionally a "break" here depending on
>        // C/C++/Java/PHP syntax vs. Pascal syntax which
>        // doesn't have fall-through
>      case '02':
>        phone.text_messaging += (
>          int(row['textmessages sent']) +
>          int(row['pages received']) +
>          int(row['textmessages sent']) +
>          int(row['pages received'])
>      ...
>      default:
>        raise WhatTheHeckIsThis()

Cleaner yes.  But, with respect, not so clean as to justify the
construct.  Following my advice you might express that switch
statement like so:

        phone.update_from_record(record)

It is, in this context, completely irrelevant observe that 'dispatch'
originally referred specifically to the dismissal of ambassadors.  It
may be slightly more to the point to tap into the wisdom of Henry Ford
and turn your design inside out.

This switch statement belongs to one guy.  One guy who wants to know
how to do everything that needs to be done to Phones no matter who
asks.  Let's install a conveyor belt instead!

Standard Disclaimer:  Untested (obviously); just a sketch; I don't
really know your problem only the code you've posted; etc etc.

First some minimal "architecture":
---
#The first class your already have. We just need one more method

class Phone (object) :
    ...
    def update_from_record (self, rec) :
        return rec.updatePhone(self)


#and then some "data source" classes

class RecType (dict) :
    def __init__ (self, row) :
        ...

class RecType_001 (RecType) :
    def updatePhone(self, phone) :
        phone.interational += Decimal(self['internationalcost'])

class RecType_002 (RecType) :
    def updatePhone(self, phone) :
        phone.text_messaging += (
                int(self['textmessages sent']) +
                int(self['pages received']) +
                ...

#and if we must ...
rectypes = {'01':RecType_001, '02': RecType_002, ...}

# The one thing I'm sure I don't understand from the code is where the
original rectypes comes into the process.
#I would prefer, if it's possible, just thowing the appropriate class
in rather than looking up this dict to instantiate.
---

Now the different providor types, previously the bane of your
existence, are your staff.  Your original code will now read something
like:

phones = {}
for row in csv.DictReader(open('data.txt', 'rb')) :
    try :
        record = rectypes[rectype](row)
    except KeyError :
        raise WhatTheHeckIsThisError('unknown rectype: %s' % rectype)
    phonenumber = record.phonenumber
    if phonenumber not in phones :
        phones[phonenumber] = Phone(phonenumber)
    phone = phones[phonenumber]
    phone.update_from_record(record)

I wonder if you agree that it's bit cleaner now?  It's an effective
solution. I'm making no representation that it's the best.

I like think largely because it contains the knowledge accessibly.  If
you have "lots of code" that deal with this kind of thing, chances are
library of core data source classage could reduce much of it to a
simple (and legible!) one liner.  A provider "enhances" their format,
or a new provider format is added, code in one class, not in every
switch they might be involved in.  But sorry, I don't mean to
patronise, I'm sure you know the spiel.

Asun



More information about the Python-list mailing list