Variable interpolation or indirection for instance attributes

Alex Martelli aleaxit at yahoo.com
Wed Feb 19 03:29:46 EST 2003


Rich wrote:

> Hello,
> 
> New to Python with a Perl background, and I've searched a good bit on
> this and can't seem to find the answer.  I'd like to know if it is
> possible to use a variable to hold the name of an instance attribute
> e.g
> 
>      x = 'street'
> self.x = '123 Albermarle Street'
> 
> such that x in self.x gets evaluated to 'street' and thus self.x
> becomes actually self.street.  I've seen a number of (possibly

If self's class defines a __setattr__, the second assignment
gets turned into a call to:

    self.__setattr__('x', '123 Albermarie Street')

Now, if this wicked class wants to use parameter 'x' to look
up the actual name of the attribute to set, nobody can stop it
from so doing.  Doing the look-up in the variable namespaces
(see vars(), locals(), globals()) is particularly weird (while
a look-up into a dictionary you have more control on might be
perhaps sort of sensible once in a while), but weirdness is not
_forbidden_ in Python -- just silly, but permitted.

Note that __setattr__'s code can't just do the setting directly
or it ends up called recursively; in a new-style class, it can
(best) delegate up e.g. to object.__setattr__(self, name, value) --
in an old-style ond, self.__dict__[name]=value is the idiom that
__setattr__ typically uses to bypass triggering itself.

> outdated) language proposals about backticks and $ symbols. None of
> these seem to work including (self.x), $self.x, self.$x, self.x$,
> `self.x`, etc.  I did see an external library mentioned that had a
> short perhaps 4 character name begining with an 'i' like 'itrp', but
> I'm stubbornly thinking that there must be a way within the language
> to do it.

The _class_ can be written, within the language, to perform this
wicked deed.  From _outside_ the class, you can't trick the class
into making self.x=whatever "black magic" unless the class wants
to.  (You can of course call setattr(self, x, "blahblah") -- that's
not black magic and not even silly, indeed quite useful -- but it
doesn't give you "black magic syntax" and the thrill thereof).


> At the same time, I'd like to find a way to initialize many class
> instance attribuites from a passed in dictionary, without just
> creating a dictionary as a n instance attribute.  And I really want to
> beat this dead horse to understand whether or not these things are
> possible, even if there is another way!

self.__dict__.update(thepassedindictionary)

should be exactly what you want (for a class that's old-style or
does not define __slots__).  For more generality (covering slots,
triggering __setattr__ when need be, and the like), loop:

    for name, value in thepassedindictionary.iteritems():
        setattr(self, name, value)

or, at your discretion, play dirty tricks:

    map(setattr, self*len(thedict), thedict.keys(), thedict.values())

I consider the loop immensely preferable, but I know some people
are heartened by the mere existence of the POSSIBILITY of doing
things in weird, hard-to-understand ways, so...


> A more likely example:
> 
>  class Address:
> 
>   def __init__(self,address=''):
> 
>       self.props = ['extended','street','locality','region','postalcode']
>       self.extended = ''
>       self.street   = ''
>       self.locality = ''
>       self.region   = ''
> 
>       if address:
>         for addresspart in address.keys():
>           self.(addresspart) = address[addresspart]    # whishful
> thinking
>                ^^^^^^^^^^^^^
>   def __getitem__(self,i):
>     key = self.props[i]
>     return eval(self.key)                              # same here
>                      ^^^^

Are you sure you want self.postalcode to remain unset by default?  Seems
strange to me.  I'd code this a bit differently:


class Address(object):

  __slots__ = 'extended', 'street', 'locality', 'region', 'postalcode'

  def __init__(self, address=''):
    for name in self.__slots__:
        setattr(self, name, '')
    for addresspart in address:
        setattr(self, addresspart, address[addresspart])

  def __getitem__(self, i):
    key = self.__slots__[i]
    return getattr(self, key)


If I wanted to let Address instances be indexable in such ways (hard
to see why I should want that), I would probably add a few more
facilities, such as a __len__ method, and the like.  But anyway,
I think this does do basically what you want -- plus, it saves some
overhead in terms of memory for each instance of Address (not a big
issue, but as long as we're listing the "slots" anyway, we might as
well do it this way -- AS LONG AS, these are the only attributes
an Address instance is allowed to have; if you want it to remain
"expando" instead [in JS terms;-)], just name the class attribute
to anything not reserved, such as, e.g., "slots" instead of using
the reserved name "__slots__" which does imply special semantics.


Alex






More information about the Python-list mailing list