[Tutor] Using __setattr__

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Sun Apr 20 18:10:02 2003


> The name, address, phone data is passed to the application, as a colon
> separated string- like this
> Name:Mr Smith;Address:123 Main St;Phone:408-123-4567;

Hi Tony,

[Off-topic note: this reminds me a little of the 'bencoding' format that
Bittorrent uses to serialize its data:

    http://bitconjurer.org/BitTorrent/protocol.html
]




> Since I don't want the application to deal with parsing the data, I
> thought I would experiment with doing it inside the class itself. It
> actually seems to work quite well, but it took me awhile to get it
> working.
>
> My __setattr__() looks like this
>         def      __setattr__(self, name, addr_str):
>                 if name in ['Name', 'Address', 'Phone' ]:
>                     name_loc = string.find(value_str, name)
>                     if name_loc != -1:
>                         pair = string.split(value_str[name_loc:], ';',1)
>                         pair1 = string.split(pair[0], ':',1)
>                         self.__dict__[name] = pair1[1]
>                     else:
>                         print"\nname %s NOT found" % name
>                         raw_input("\nPAUSED")

Ah, I think I understand now.


The code can actually be rewritten to avoid __setattr__ altogether: how
about just adding a method called initFromString()?

###
    def initFromString(self, name, addr_str):
        if name in ['Name', 'Address', 'Phone' ]:
            name_loc = string.find(value_str, name)
            if name_loc != -1:
                pair = string.split(value_str[name_loc:], ';',1)
                pair1 = string.split(pair[0], ':',1)
                setattr(self, name, pair1[1])
            else:
                print"\nname %s NOT found" % name
                raw_input("\nPAUSED")
###

It's not really an adding: it's more like a renaming of your __setattr__()
definition.  *grin* In many cases, we want to avoid the magical nature of
__setattr__().  Renaming it to something nonmagical is one way to make the
code simpler.


We can call initFromString() with a simple method call, like this:

###
s = "Name:Mr Smith;Address:123 Main St;Phone:408-123-4567;"
address = Address()
address.initFromString('Name', s)
address.initFromString('Address', s)
address.initFromString('Phone', s)
###


Once we have this, we might notice that we may want to initialize all of
the values in our attribute from the string at once.  So the code
reorganization allows us to consider if something like:

###
s = "Name:Mr Smith;Address:123 Main St;Phone:408-123-4567;"
address = Address()
address.initAllFromString(s)      ## maybe something like this is a good
                                  ## idea... ?
###

so that you may not need to explicitely name the attributes that you'd
like to set.  If you knew all the attributes in advance, there wouldn't be
a point to read them dynamically, since we'd be able to do something like:

    address.Name = lookupValueInString('Name', s)
    address.Address = lookupValueinString('Address', s)
    ...

which would be easier for a person to read and understand.


Modifying __setattr__() can often unnecessarily complicate the definition
of a class, because it introduces an intrusive change to the way we
initialize our object's attributes.  Unless we're doing something really
special, we may find it's a good idea to avoid redefining __setattr__()
in Python.


By the way, Jeff Shannon correctly pointed out that, in most cases, we
also don't have to manually futz with __dict__ to add dynamic attributes.

    http://mail.python.org/pipermail/tutor/2003-April/021815.html

I stand properly chastised.  *grin* It's often a lot simpler to use the
getattr() and setattr() functions.  So I've subsituted:

                self.__dict__[name] = pair1[1]

with:

                setattr(self, name, pair1[1])

so that things look less magical.


I hope this helps!