0

I'm having an issue trying to modify strings within a list. I have a for loop set up to select each string item but I'm not able to modify it. I thought it might be a global/local scope issue or a muteable/immutable type issue but I've looked through documentation and I can't find out why I wouldn't be able to modify the string with the code I have in place right now.

The little program is not complete but the idea is to take my tableData variable and print it to look like

  apples Alice  dogs
 oranges   Bob  cats
cherries Carol moose
  banana David goose

This is a problem I've been on for a combined 5 hours, and its from Automate the Boring Stuff with Python chapter 6 practice project found at: https://automatetheboringstuff.com/chapter6/

Here is my code (issue being at the very bottom):

# This program will take a list of string lists and print a table of the strings.

from copy import deepcopy
tableData = [['apples', 'oranges', 'cherries', 'banana'],
             ['Alice', 'Bob', 'Carol', 'David'],
             ['dogs', 'cats', 'moose', 'goose']]


def print_table():
'''This function will take any list of lists and print it in a table format
with evenly spaced columns.'''
    # First I wanted to identify the max length of my columns without making changes to my original list
    copied_list = deepcopy(tableData)  
    for copied_innerlist in range(len(copied_list)):
        copied_list[copied_innerlist].sort(key=len, reverse=True)  # sort each inner list by length
    colWidths = [len(copied_innerlist[0]) for copied_innerlist in copied_list]  # change the column width variable to reflect the length of longest string in each inner list

    # Now that I've stored my columns widths I want to apply them to all the strings without affecting the original
    final_list = deepcopy(tuple(tableData))

    for item in range(len(final_list)):
        for inner_item in final_list[item]:
            inner_item = inner_item.rjust(colWidths[item])
            '''WHY WONT THIS RJUST METHOD STICK '''


    print(final_list)
    """this just looks like that original list! :( """

print_table()

4 Answers4

2

In Python, strings are immutable. When you do

inner_item = inner_item.rjust(colWidths[item])

you're just creating a new string and making the same label reference it. The old value (stored in the list) remains unchanged. You need to either modify list elements using indices:

for i in range(len(final_list)):
    for j in range(len(final_list[i])):
        final_list[i][j] = final_list[i][j].rjust(colWidths[i])

Or, better yet, build a new list using a list comprehension:

final_list = [inner_item.rjust(colWidths[item])
              for item in tableData for inner_item in item]

The latter is not only more concise, but also exempts you from the need to copy the original list.

Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
1

You don't need to modify the strings in your tableData with str.rjust (you can easily avoid that painful deepcopy). I suggest you apply string formatting for printing:

tableData = [['apples', 'oranges', 'cherries', 'banana'],
             ['Alice', 'Bob', 'Carol', 'David'],
             ['dogs', 'cats', 'moose', 'goose']]

def print_table(data):
    lens = [max(map(len, x)) for x in data]  # get max length per column        
    lst = zip(*data)                         # transpose table
    for x in lst:
        print("{:>{l[0]}} {:>{l[1]}} {:>{l[2]}}".format(*x, l=lens))

print_table(tableData)

  apples Alice  dogs
 oranges   Bob  cats
cherries Carol moose
  banana David goose
Moses Koledoye
  • 77,341
  • 8
  • 133
  • 139
0

You can use zip:

tableData = [['apples', 'oranges', 'cherries', 'banana'],
         ['Alice', 'Bob', 'Carol', 'David'],
         ['dogs', 'cats', 'moose', 'goose']]


new = list(map(list, zip(*tableData)))

for i in new:
    print i

Output:

['apples', 'Alice', 'dogs']
['oranges', 'Bob', 'cats']
['cherries', 'Carol', 'moose']
['banana', 'David', 'goose']
Ajax1234
  • 69,937
  • 8
  • 61
  • 102
-1

That is one of the reasons why you do not modify a loop while iterating it. It can lead to problems if you try to change and compute the iterables inside the loop simultaneously as mentioned here.

Never alter the container you're looping on, because iterators on that container are not going to be informed of your alterations and, as you've noticed, that's quite likely to produce a very different loop and/or an incorrect one.

If you still want to do it, access it using index like this:

for item in range(len(final_list)):
        for index,inner_item in enumerate(final_list[item]):
            final_list[item][index] = inner_item.rjust(colWidths[item])

This one will work. Hope this helps!

Rudresh Panchal
  • 980
  • 4
  • 16
  • So this is just a best practice then to not be changing items with for loops? Not sure I understood you correctly - I think I was just modifying a list while iterating over it and didnt expect it to be wrong! I just got frustrated because I wasn't taught zip(), map() or list comprehensions yet so I was trying to use the skills I had. I did use a list comprehension near the start of my function but I didnt fully understand it even after the videos I watched on it – Eduard Tepes Jun 25 '17 at 21:49