See
See

Reputation: 81

Python format print with a list

Which is the most pythonic way to produce my output. Let me illustrate the behavior I'm trying to achieve. For a project of my I'm building a function that takes different parameters to print an the output in columns. Example of the list its receives.

[('Field', 'Integer', 'Hex'), 
('Machine;', 332, '0x14c'), 
('NumberOfSections;', 9, '0x9'), 
('Time Date Stamp;', 4, '0x4'), 
('PointerToSymbolTable;', 126976, '0x1f000')

** The size of items can differ (Only 3 items per tuple now, can be 4 for another list or any number**

The output should be something like this

Field                                                     Integer      Hex           
-------------------------------------------------------------------------------
Machine;                                                  332          0x14c        
NumberOfSections;                                         9            0x9          
Time Date Stamp;                                          4            0x4          
PointerToSymbolTable;                                     126976       0x1f000

For working purposes I created a list which only contains the header fields: This isn't necessary but it made it a little bit easier trying stuff out

Header field is ['Field', 'Integer', 'Hex']  

The first tuple in the list declares the so called "Header fields" as shown in the list example. For this case there are only 3 items, but this can differ from time to time. So I tried to calculate the size of items with:

length_container_header = len(container[0])

This variable can be used to correctly build up the output. Building the header "print" I would build something like this.

print("{:21} {:7} {:7}".format(header_field[0], header_field[1], header_field[2]))

Now this is a manual version on how it should be. As you noticed the header field "Field" is shorter then PointerToSymbolTable in the list. I wrote this function to determine the longest item for each position in the list

container_lenght_list = []    
    local_l = 0    
    for field in range(0, lenght_container_body):
        for item in container[1:]:
            if len(str(item[field])) > local_l:
                local_l = len(str(item[field]))
            else:
                continue
        container_lenght_list.append(str(local_l))
        local_l = 0

Produces a list along the lines like [21, 7, 7] in this occasion. creating the format string can be done pretty simple,

formatstring = ""

for line in lst:
    formatstring+= "{:" + str(line) +"}"

Which produces string:

 {:21}{:7}{:7}

This is the part were a run into trouble, how can I produce the last part of the format string? I tried a nested for loop in the format() function but I ended up with all sort of Errors. I think it can be done with a for loop, I just can't figure out how. If someone could push me in the right direction for the header print I would be very grateful. Once I figured out how to print the header I can pretty much figure out the rest. I hope I explained it well enough

With Kind Regards,

Upvotes: 5

Views: 5943

Answers (3)

Abhijit
Abhijit

Reputation: 63707

Formatting data in tabular form requires four important steps

  1. Determine the field layout i.e. representing data row wise or column wise. Based on the decision you might need to transpose the data using zip
  2. Determine the field sizes. Unless you wan;t to hard-code the field size (not-recommend), you should actually determine the maximum field size based on the data, allowing customized padding between fields. Generally this requires reading the data and determining the maximum length of the fields [len(max(map(str, field), key = len)) + pad for field in zip(*data)]
  3. Extract the header row. This is easy as it only requires indexing the 0th row i.e. data[0]
  4. Formatting the data. This requires some understanding of python format string

Implementation

class FormatTable(object):
    def __init__(self, data, pad = 2):
        self.data = data
        self.pad = pad
        self.header = data[0]
        self.field_size = [len(max(map(str, field), key = len)) + pad 
                           for field in zip(*data)]
        self.format = ''.join('{{:<{}}}'.format(s) for s in self.field_size)
    def __iter__(self):
        yield ''.join(self.format.format(*self.header))
        yield '-'*(sum(self.field_size) + self.pad * len(self.header))
        for row in data[1:]:
            yield ''.join(self.format.format(*row))

Demo

for row in FormatTable(data):
    print row

Field                  Integer  Hex      
-----------------------------------------------
Machine;               332      0x14c    
NumberOfSections;      9        0x9      
Time Date Stamp;       4        0x4      
PointerToSymbolTable;  126976   0x1f000 

Upvotes: 1

falsetru
falsetru

Reputation: 368954

You can use * to unpack argument list:

container = [
    ('Field', 'Integer', 'Hex'), 
    ('Machine;', 332, '0x14c'), 
    ('NumberOfSections;', 9, '0x9'), 
    ('Time Date Stamp;', 4, '0x4'), 
    ('PointerToSymbolTable;', 126976, '0x1f000')
]
lengths = [
    max(len(str(row[i])) for row in container) for i in range(len(container[0]))
] # => [21, 7, 7]
# OR  lengths = [max(map(len, map(str, x))) for x in zip(*container)]
fmt = ' '.join('{:<%d}' % l for l in lengths)
# => '{:<21} {:<7} {:<7}'    # < for left-align

print(fmt.format(*container[0]))  # header
print('-' * (sum(lengths) + len(lengths) - 1))  # separator
for row in container[1:]:
    print(fmt.format(*row))  # <------- unpacking argument list
    # similar to   print(fmt.format(row[0], row[1], row[2])

output:

Field                 Integer Hex
-------------------------------------
Machine;              332     0x14c
NumberOfSections;     9       0x9
Time Date Stamp;      4       0x4
PointerToSymbolTable; 126976  0x1f000

Upvotes: 5

Robᵩ
Robᵩ

Reputation: 168616

I don't know if it is "Pythonic", but you can use pandas to format your output.

import pandas as pd

data = [('Field', 'Integer', 'Hex'),
('Machine;', 332, '0x14c'),
('NumberOfSections;', 9, '0x9'),
('Time Date Stamp;', 4, '0x4'),
('PointerToSymbolTable;', 126976, '0x1f000')]

s = pd.DataFrame(data[1:], columns=data[0])
print s.to_string(index=False)

Result:

                 Field  Integer      Hex
              Machine;      332    0x14c
     NumberOfSections;        9      0x9
      Time Date Stamp;        4      0x4
 PointerToSymbolTable;   126976  0x1f000

Upvotes: 0

Related Questions