0

I'm trying to debug some Python 2.7.3 code to loop through a list of items and convert each to a string:

req_appliances = ['9087000CD', 'Olympus', 185]
for i in range(0, len(req_appliances)):
    req_appliances[i] = str(req_appliances[i])
print req_appliances

The output is as follows:

['9087000CD', 'Olympus', '185']

In the example above, I've set the value of req_appliances explicitly to test the loop. In the actual code, req_appliances is an argument to a function. I do not know the type of the argument at runtime, but it appears to be a list of scalar values. I do know that when I invoke the function, I see the following error message:

File ".../database.py", line 8277, in get_report_appliance_list
    req_appliances[i] = str(req_appliances[i])
TypeError: 'str' object does not support item assignment

I'm trying to deduce for what values of argument req_appliances it would be possible for this error condition to arise. It seems to me that all of the values are scalar and each (even if immutable) should be a valid LHS expressions in an assignment. Is there something I'm missing here? Here is the code in context, in the function in which it is defined:

def get_report_appliance_list(self, req_appliances, filter_type=None):

    appliances = {}
    appliance_list = []

    if filter_type != None:
        if filter_type not in ('appliances', 'servers'):
            raise ValueError("appliance filter_type must be one of 'appliances' or 'servers'")

    active_con = self.get_con()
    if active_con is None:
        raise Exception('No database connections are available.')
    con = None

    in_expr_items = ''
    if req_appliances != None:
        # Create a string like '(%s, %s, ...)' to represent
        # the 'in' expression items in the SQL.
        print(req_appliances)
        for i in range(0, len(req_appliances)):
            req_appliances[i] = str(req_appliances[i])
            in_expr_items += '%s,'
        in_expr_items = '(' + in_expr_items[:-1] + ') '
Michael G. Morey
  • 318
  • 1
  • 5
  • 13
  • I am guessing that the req_applieances that causes this is a string - you were expecting a list but in this case it is a string. Can you find the offending line – PyNEwbie Mar 04 '14 at 19:17
  • I'm guessing you're passing an actual string... Without seeing your function defintion (and the arguments), it's hard to tell, but it may be you want variable arguments `def your_func(*req_appliances)` for instance... – Jon Clements Mar 04 '14 at 19:18

2 Answers2

3

An str acts like a sequence type (you can iterate over it), but strings in Python are immutable, so you can't assign new values to any of the indices.

I expect what's happening here is that you're trying to run this when req_appliances is a str object.

I came up with two ways to fix this:

First, just check if it's a str before you iterate over it:

if isinstance(req_appliances, basestring):
  return req_appliances

Second, you could check each item to see if it's already a string before trying to assign to it.

req_appliances = ['9087000CD', 'Olympus', 185]
for i in range(0, len(req_appliances)):
  if req_appliances[i] != str(req_appliances[i]):
    req_appliances[i] = str(req_appliances[i])
print req_appliances

Here, I'm actually checking whether the member is equal to its string representation. This is true when you iterate over strings.

>>> a = 'a'
>>> a[0] == str(a[0])
True
Community
  • 1
  • 1
Sam Mussmann
  • 5,883
  • 2
  • 29
  • 43
  • I first implemented the check for each item. That prevented the error. I then implemented the check for isinstance(..., basestring). That revealed an instance where the caller was passing a string instead of a list of scalars. We should do more run-time type checking to prevent these sorts of subtle errors. Thanks! – Michael G. Morey Mar 04 '14 at 19:51
  • No problem! And I'm glad both parts were useful. – Sam Mussmann Mar 04 '14 at 20:31
1

This is not really an answer to your question, but a style advise. If you happen to use for i in range(0, len(something)) a lot you should either use for i, obj in enumerate(something), map(func, something) or a list comprehension [func(x) for x in something].

Another red flag is the use of string += inside a loop. Better create an array and join it. This also eliminates the need to do stuff like [-1] in order to get rid of trailing commas.

Regarding your code you could simplify it a lot:

def get_report_appliance_list(self, req_appliances, filter_type=None):

    appliances = {}
    appliance_list = []

    if filter_type not in (None, 'appliances', 'servers'):
        raise ValueError("appliance filter_type must be one of 'appliances' or 'servers'")

    active_con = self.get_con()
    if active_con is None:
        raise Exception('No database connections are available.')

    # Create a string like '(%s, %s, ...)' to represent
    # the 'in' expression items in the SQL.
    in_expr_items = ','.join(['%s'] * len(req_appliances)
    req_appliances = map(str, req_appliances)

...

Apart from that I would recommend that get_con() throws so you do not have to check for None in your code.

bikeshedder
  • 7,337
  • 1
  • 23
  • 29