2
\$\begingroup\$

I'm trying to define a function which takes an array and prints it to a predefined total width in as many columns as needed. Think of the way ls prints files for example.

Here is the kind of output I am looking for:

$ ./print-columns.py 


Files
    /home/stephen/dev/src/cmake-browser/cmakeclient.cpp    /home/stephen/dev/src/cmake-browser/projectsources.cpp
    /home/stephen/dev/src/cmake-browser/main.cpp           /home/stephen/dev/src/cmake-browser/includesmodel.cpp 
    /home/stephen/dev/src/cmake-browser/mainwindow.cpp     /home/stephen/dev/src/cmake-browser/definesmodel.cpp  
    /home/stephen/dev/src/cmake-browser/debugwidget.cpp    /home/stephen/dev/src/cmake-browser/highlighter.cpp   
    /home/stephen/dev/src/cmake-browser/projecttree.cpp    /home/stephen/dev/src/cmake-browser/helpviewer.cpp    
    /home/stephen/dev/src/cmake-browser/projectmodel.cpp   /home/stephen/dev/src/cmake-browser/main.cpp          
    /home/stephen/dev/src/cmake-browser/projectcode.cpp    /home/stephen/dev/src/cmake-browser/helpviewer.cpp    
    /home/stephen/dev/src/cmake-browser/projectdetail.cpp 


Targets
    KF5ItemModels                    kdescendantsproxymodel_smoketest klinkitemselectionmodeltest     
    krecursivefilterproxymodeltest   kselectionproxymodel_smoketest   kselectionproxymodeltest        
    proxymodeltestapp                proxymodeltestsuite              testmodelqueuedconnections

I created the following script to do that. I'm trying to brush up on my Python, and re-learn how to write pythonic code in the most modern way (latest Python 3), and it doesn't feel very pythonic (and it's not the kind of code I usually write).

#!/usr/bin/env python3

import sys, subprocess, json, math

def col_print(title, array):
  print()
  print()
  print(title)


  indent = " " * 4

  if not array:
    print(indent + "<None>")
    return

  padsize = 1
  # Hardcoded for this example
  termwidth = 150

  maxitemwidth = len(max(array, key=len))

  numCols = int((termwidth + padsize) / (maxitemwidth + padsize))

  numRows = int(math.ceil(len(array) / float(numCols)))

  pad = " " * padsize

  for index in range(numRows):
    print(indent + pad.join(item.ljust(maxitemwidth) for item in array[index::numRows]))

col_print("Files",
          ["/home/stephen/dev/src/cmake-browser/cmakeclient.cpp",
          "/home/stephen/dev/src/cmake-browser/main.cpp",
          "/home/stephen/dev/src/cmake-browser/mainwindow.cpp",
          "/home/stephen/dev/src/cmake-browser/debugwidget.cpp",
          "/home/stephen/dev/src/cmake-browser/projecttree.cpp",
          "/home/stephen/dev/src/cmake-browser/projectmodel.cpp",
          "/home/stephen/dev/src/cmake-browser/projectcode.cpp",
          "/home/stephen/dev/src/cmake-browser/projectdetail.cpp",
          "/home/stephen/dev/src/cmake-browser/projectsources.cpp",
          "/home/stephen/dev/src/cmake-browser/includesmodel.cpp",
          "/home/stephen/dev/src/cmake-browser/definesmodel.cpp",
          "/home/stephen/dev/src/cmake-browser/highlighter.cpp",
          "/home/stephen/dev/src/cmake-browser/helpviewer.cpp",
          "/home/stephen/dev/src/cmake-browser/main.cpp",
          "/home/stephen/dev/src/cmake-browser/helpviewer.cpp"]
)

col_print("Targets",
          ["KF5ItemModels",
          "krecursivefilterproxymodeltest",
          "proxymodeltestapp",
          "kdescendantsproxymodel_smoketest",
          "kselectionproxymodel_smoketest",
          "proxymodeltestsuite",
          "klinkitemselectionmodeltest",
          "kselectionproxymodeltest",
          "testmodelqueuedconnections"]
)

Can you help write a better col_print function?

\$\endgroup\$
2
\$\begingroup\$

There are a few style changes in Python. The main one is Python uses 4 spaces, not 2, as it's standard indent size. This is important as indent size matters in Python. Also most variables are snake_case. So my code examples may have small changes.


  1. I would recommend splitting the displaying and formatting into separate functions. This is as then you can manipulate the output. Which can be nice.

    This means that you would have to change your returns.

    Changing this allows you to remove the two empty lines at the beginning, for having to explicitly call print.
    And you can print both files and targets at the same time:

    print("\n\n".join([
        col_print("Files",
            [...]
        ),
        col_print("Targets",
            [...]
        )
    ]))
    
  2. You could pass termwidth and padsize as default values. This allows you to change them if you wish at a later point.

    def col_print(title, array, pad_size=1, term_width=150):
        ...
    
  3. You may want to change maxitemwidth so there is only one len. It makes the code slightly easier to understand. This is as I originally was a bit confused when I saw the len(max(...)).

    max_item_width = max(map(len, array))
    
  4. Division changed in Python3.

    Rather than doing int(a / b) you can do a // b. Python2 division can be nice at times, and so they kept it in.

    Python3 division automatically casts a and b to floats.

    In short this means that you don't have to do things like a / float(int(b / c)). Instead you can do a / (b // c). Which is much more readable.

At this point the code looks quite nice:

def col_print(title, array, term_width=150, pad_size=1):
    indent = " " * 4
    pad = " " * pad_size
    title += "\n"

    if not array:
        return title + indent + "<None>"

    max_item_width = max(map(len, array))
    num_rows = int(math.ceil(len(array) / ((term_width + pad_size) // (max_item_width + pad_size))))

    return title + "\n".join(
        indent + pad.join(item.ljust(max_item_width) for item in array[index::num_rows])
        index in range(num_rows)
    )
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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