4
\$\begingroup\$

The project outline:

Write a function named printTable() that takes a list of lists of strings and displays it in a well-organized table with each column right-justified. Assume that all the inner lists will contain the same number of strings.

My code:

def demo():
    tableData = [['apples', 'oranges', 'cherries', 'banana'],
                ['Alice', 'Bob', 'Carol', 'David',],
                ['dogs', 'cats', 'moose', 'goose']]
    return tableData
    
def printTable(tableData):
    colWidths = [0] * len(tableData)
    for i in range(len(tableData)):
        colWidths[i] = len(max(tableData[i], key=len))
    for x in range(len(tableData[0])):
        for y in range(len(tableData)):
            print(tableData[y][x].rjust(colWidths[y]), end=' ')
        print(end='\n')

printTable(demo())

This took me a while to get my head around. I ended up using max() which the book hasn't covered yet but otherwise I think this is what the author had in mind.

Ways to make it better?

\$\endgroup\$
1
  • \$\begingroup\$ print('\n'.join(' | '.join(row)for row in zip(*((word.rjust(width)for word in col)for col,width in zip(tableData,(max((len(word)for word in col))for col in tableData)))))) \$\endgroup\$
    – gboffi
    May 9, 2022 at 13:50

1 Answer 1

5
\$\begingroup\$

One rarely needs to iterate over collections via indexes. Iterate over the thing itself (the list, tuple, or dict), not the indexes-of-the-thing.

# No.
for i in range(len(table)):
    col = table[i]
    ...

# Yes.
for col in table:
    ...

# Yes (if you also need the indexes).
for i, col in enumerate(table):
    ...

Functions should return data, not print. Your book is giving bad direction by encouraging students to think about functions as printing utilities. Printing is a trivial operation and has no bearing on the interesting parts of this exercise -- namely computing the column widths and transposing the data. The function you write should take data as input (the column-oriented table data) and should return data (width-padded, row-oriented table data). The function below illustrates one way to achieve that. Much more important than the specific code is the general principle: write functions that take data and return data, not functions that have side effects (like printing). Among other advantages, a function like this can be easily subjected to automated testing.

def padded_table(table):
    widths = [
        max(len(cell) for cell in col)
        for col in table
    ]
    return [
        [cell.rjust(w) for cell, w in zip(row, widths)]
        for row in zip(*table)                          # Transpose the table.
    ]

Do the printing elsewhere. After writing a sensible data-centric function, the printing is completely uninteresting.

def main():
    table = [
        ['apples', 'oranges', 'cherries', 'banana'],
        ['Alice', 'Bob', 'Carol', 'David',],
        ['dogs', 'cats', 'moose', 'goose'],
    ]
    for row in padded_table(table):
        print(' '.join(row))          # Super boring!

if __name__ == '__main__':
    main()
\$\endgroup\$
4
  • \$\begingroup\$ Would you mind clarifying your last statement, where you mention "After writing a sensible data-centric function, the printing completely uninteresting." The code then has a comment that reads # Super boring! What is it that you mean by this statement, and do you recommend a better way of handling printing operations for more complex code? \$\endgroup\$
    – Ramza
    May 8, 2022 at 16:46
  • 1
    \$\begingroup\$ @Ramza I'm just goofing around with super-boring comment (and indirectly critiquing the Automate The Boring Stuff book). However, the serious point is to do the printing in the part of the program that has almost no complexity, almost no algorithm. In the code I sketched above, nothing much is happening in main(), so it's a decent place to orchestrate input (asking the user stuff) and output (things like printing). The other code has algorithmic complexity and should be free of side effects like printing (because we might want to test it, for example). \$\endgroup\$
    – FMc
    May 8, 2022 at 16:50
  • \$\begingroup\$ I'd still argue that a print_table() function, that simply contains your for loop, would be a better choice, in terms of readability and maintainability. As soon as you want to print a table more than once, a function would be the obvious choice anyway. \$\endgroup\$ May 9, 2022 at 12:47
  • \$\begingroup\$ @infinitezero To print the table more than once, write a function to do the looping/joining, but keep the printing out of it: build_table(table). Again, a primary reason is testability. Push side-effects to the outer, non-algorithmic edge of the program, where nothing much is going on. See Gary Bernhardt for a more extended discussion of such ideas. \$\endgroup\$
    – FMc
    May 9, 2022 at 14:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.