How do I combine instance+string for variable

Bjorn Pettersen BPettersen at NAREX.com
Sat Aug 2 02:12:36 EDT 2003


> From: Marc [mailto:mnations at airmail.net] 
> 
> Hi all,
> 
> I can't remember how to do this.
> 
> I have several instances of telnet connections that I label
> conn2,conn3, etc. Later when I want to scroll through all of these I
> wanted to do something like this:
> 
>     for int in range(2, 9):
>             use... conn+str(int) {I'm passing it into another
> function}
> 
> I can't get it to work. I've tried using setattr and eval, but 
> nothing seems to work. Can I get a little help.

Enumerating something by using variable names containing the index
quickly becomes unweildly, storing the enumeration in a collection
object is usually much more managable. These hand numbered
pseudo-collections are most frequently encountered in beginning
programmers' design, presumably because it's the way you'd do it by
hand, however the general idea sometimes make tedious things trivial...

If you can switch to using a collection, there are several variations...
Assuming sequential creation of connection objects possibly in different
code locations (scopes), use append to build the list:

  conn = []
  conn1 = ...
  conn.append(conn1)
  conn2 = ...
  conn.append(conn2)
  ...

or simpler -- if connections are all created in the same scope,
construct through list literal:

  conn1 = ...
  conn2 = ...
  ...
  conn = [conn1, conn2, ...]

or alternatively if connections are created in different places
out-of-order (introducing the need to deal with None -- easily
alleviated by a trivial list subclass to deal with error checking,
append capability, support for sparceness, etc):

  conn = [None] * numConnections
  ...
  conn[5] = createConnObject(...)

For all of the above you'll use it the obvious way:

  for cn in conn[2:9]:
      cn.someMethod(...)

Note that using collections lose the "name property" of the object, i.e.
in the general case there is no way to go from the connection object
(e.g. cn in the for loop above) to the variable name the object was
initially created under). That is normally the underlying reason for
more interesting uses of named values. For enumerations like yours, it
shouldn't be a problem. 

If you do need the stringified name, a layer of indirection is required
(and introduces a synchronization problem):

  conn1 = createConnObject(...)
  conn2 = createConnObject(...)
  ...
  conn = {
    'conn1' : conn1,
    'conn2' : conn2,
    'conn3' : ...
  }

  for i in range(2,9):
      conn_name = 'conn' + `i`   # construct var name
      cn = conn[conn_name]       #
      print 'using:', conn_name
      cn.someMethod(...)

And yes, I will also answer your real question <wink>. Giving a variable
name to eval() will return the object bound to that variable in the
current scope (implicit):

  conn1 = ...
  conn2 = ...
  ...

  for i in range(2,9):
      cn = eval('conn' + `i`)
      cn.someMethod(...)

(note: using eval() this way is not a security risk.)

Using vars(), locals(), globals(), or a module prefix gives you the
possiblity of explicitly specifying the scope. 

The scope issues, along with the frequent name constructions, makes a
class based solution seem more appropriate and flexible, e.g. something
like:

class NamedValueCollection(object):
    def __sortedNames(self):
        """Sorted list of contained values."""
        # I'd still like to be able to say self.__dict__.keys().sort(), 
        # I'm grown-up enough to know it could be expensive and still
        # not care <wink>.
        names = self.__dict__.keys()
        names.sort()
        return names

    def __setitem__(self, key, obj):
        if isinstance(key, (int, long)):
            # key += 1 here, if you want the indexing to be 1-based
            names = self.__sortedNames()
            self.__dict__[names[key]] = obj
        elif isinstance(key, slice):
            raise TypeError('Slice assignment left as an excercise.')
        else:
            self.__dict__[key] = obj
                
    def __getitem__(self, key):
        """Hybrid __getitem__, supporting the sequence protocol
(number/slice 
           keys) and the mapping protocol (string key).
        """
        if isinstance(key, slice):
            # coll[a:b], returns (name, value) tuples, cf. __iter__
below.
            import itertools as itools
            return itools.islice(self, key.start, key.stop)
        elif isinstance(key, (int, long)): # not quite unified yet :-)
            # coll[5]
            names = self.__sortedNames()
            return self.__dict__[names[key]]
        else:
            # coll['conn1']
            return self.__dict__[key]
        
    def __iter__(self):
        for name in self.__sortedNames():
            yield (name, self[name])

conn = NamedValueCollection()

# set trough string value of variable name
conn['cn1'] = createConnObject(...)

# set trough attribute
conn.cn2 = createConnObject(...)

# get through attribute
print conn.cn1

# get through variable name
print conn['cn2']

# get through index (zero based)
print conn[1]

# replace through index (zero based)
conn[2] = createConnObject(...)

# iterate (calling __iter__)
for varname, cn in conn:
    print varname, cn

# iterate through slice (calling __getitem__)
for varname, cn in conn[:1]:
    print varname, cn

hth,
-- bjorn

ps: to relight another religious issue, this is a place where type
declarations could be useful. Not for type checking, but
overloading/pattern matching (as an excercise, try to give the type of
__getitem__ :-)  Overloading __lt__ on type objects to mean isinstance,
<, <=, or <: are normally used to indicate subtype-of relationships,
would make the current version look better to a type-theorist, but is
probably used too infrequently to be justified...







More information about the Python-list mailing list