Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Table Maker Class
#1
Hello everyone,

I've recently just started learning Python and programming as a whole and while tinkering with some MySQL and Python i noticed that MySQL outputs query results in a nice tabulated format and there was no such functionality in Python by default ( didn't know about modules like tabulate and pretty table and how to use them at that time Doh ), so i was striven to make something myself and i've put together some hacky/ slashy code to the best of my current ability while trying to learn how things like classes etc. work Tongue .

I know that the code looks bad and many things that could have been achieved using simpler/ better means were not. Please feel free to criticize the code as it will help me learn.

class tablemaker:
    """
    anylist :   Any list, tuple, set or string to be printed in ascii enclosed boxes (two dimensional maximum)
    title   :   Optional title for the list to be printed on top of the data
    headers :   Optional headers to be printed (Provide as a one dimensional list with each list item representing a column heading)
    """
    def __init__(self, provideddata, title, providedheaders):
        self.anydata=list(map(list,self.__all_strings(provideddata[:])))
        self.__addresses=self.__generate_addresses()
        self.title=title
        if isinstance(self.title,str) != True:
            raise TypeError('String expected for argument#2')
        self.headers=providedheaders
        if self.headers!="":
            self.headers=self.__all_strings(providedheaders)
        self.table=[]
        self.columns=0
        self.rows=0
        self.__process(self.__all_strings(self.anydata))

    def __generate_addresses(self):
        addressrange=[]
        maincounter=0
        innercounter=0
        for each in self.anydata:
            if isinstance(each,(list,tuple,set)):
                for eachinner in each:
                    addressrange.append([maincounter,innercounter])
                    innercounter=innercounter+1
                maincounter=maincounter+1
                innercounter=0
            else:
                addressrange.append([maincounter,innercounter])
                maincounter=maincounter+1
        return addressrange
        

    def value(self,r,c,value):
        if value!="":
            if [r,c] in self.__addresses:
                try:
                    self.anydata[r][c]=value
                except:
                    self.anydata[r]=value
            else:
                columnfound=[0,False]
                for loop in range(c,-1,-1):
                    if [r,loop] in self.__addresses:
                        columnfound=[loop,True]
                        break
                if columnfound[1]==True:
                    if not isinstance(self.anydata[r],list):
                        self.anydata[r]=[self.anydata[r]]
                    for loop in range(columnfound[0]+1,c):
                        self.anydata[r].append("")
                    self.anydata[r].append(value)
                else:
                    rowfound=[0,False]
                    for loop in range(r,-1,-1):
                        if [loop,0] in self.__addresses:
                            rowfound=[loop,True]
                            break
                    if rowfound[1]==True:
                        for loop in range(rowfound[0]+1,r+1):
                            self.anydata.append([])                            
                        for loop in range(1,c+1):
                            self.anydata[r].append("")
                        self.anydata[r].append(value)
            self.__addresses=self.__generate_addresses()
        else:
            raise ValueError('Invalid data provided in argument#3')

    def update(self):
        self.__process(self.__all_strings(self.anydata))
        
    def print(self):
        print("\n".join(self.table))

    def __all_strings(self,data):
        """Courtesy of Yoriz"""
        if type(data) == list:
            items = []
            for item in data:
                items.append(self.__all_strings(item))
            return items
        elif type(data) == tuple:
            return tuple(self.__all_strings(list(data)))
        elif type(data) == dict:
            new_dict = {}
            for key, value in data.items():
                new_dict[self.__all_strings(key)] = self.__all_strings(value)
            return new_dict
        elif type(data) == set:
            return set(self.__all_strings(list(data)))
        else:
            return str(data).strip()

    def __process(self,receivedlist):
        anylist=[]
        anylist=receivedlist[:]
        anylist=[i if i!=[] else "" for i in anylist]
        if len(anylist)>0:
            if self.headers!="":
                anylist.insert(0,self.headers)
            datalist=[]
            maxlen=len(self.title)
            collen=[]
            maxsubentry=1
            for entries in anylist:
                if isinstance(entries,(list,tuple)):
                    if len(entries)>maxsubentry:
                        maxsubentry=len(entries)
                    for subentry in entries:
                        if len(subentry)>maxlen:
                            maxlen=len(subentry)
                if len(entries)>maxlen:
                    maxlen=len(entries)
            collen=[0 for each in range(0,maxsubentry)]
            for entries in anylist:
                if isinstance(entries,(list,tuple)):
                    for subentry in entries:
                        if len(subentry)>collen[entries.index(subentry)]:
                            collen[entries.index(subentry)]=len(subentry)
                else:
                    if len(entries)>collen[0]:
                        collen[0]=len(entries)
            checklen=0
            for each in collen:
                checklen+=each
            if maxlen>checklen:
                collen[-1]=collen[-1]+(maxlen-checklen)
            else:
                maxlen=checklen
            if self.title!="":
                printstring="+"+"-"*(maxlen+maxsubentry-1)+"+"
                datalist.append(printstring)
                printstring="|"+self.title.upper()+" "*((maxlen+maxsubentry-1)-len(self.title))+"|"
                datalist.append(printstring)
                printstring="|"+"-"*(maxlen+maxsubentry-1)+"|"
                datalist.append(printstring)
            else:
                printstring="+"+"-"*(maxlen+maxsubentry-1)+"+"
                datalist.append(printstring)            
            for entries in anylist:
                if isinstance(entries,(list,tuple)):
                    printstring=""
                    counter=0
                    for subentry in entries:
                        printstring=printstring+"|"+subentry+" "*(collen[counter]-len(subentry))
                        counter+=1
                    if len(entries)<maxsubentry:
                        for count in range(len(entries)+1, maxsubentry+1):
                            printstring=printstring+"|"+" "*(collen[counter])
                            counter+=1
                    printstring=printstring+"|"
                    datalist.append(printstring)
                else:
                    printstring=""
                    printstring=printstring+"|"+entries+" "*(collen[0]-len(entries))
                    for count in range(0,maxsubentry-1):
                        printstring=printstring+"|"+" "*(collen[count+1])
                    printstring=printstring+"|"
                    datalist.append(printstring)
            printstring="+"+"-"*(maxlen+maxsubentry-1)+"+"
            datalist.append(printstring)
            if self.title!="":
                rowlen=len(datalist)-4
            else:
                rowlen=len(datalist)-2
            if self.headers!="":
                    rowlen=rowlen-1
            if self.headers!="":
                printstring="+"+"-"*(maxlen+maxsubentry-1)+"+"
                if self.title!="":
                    datalist.insert(4,printstring)
                else:
                    datalist.insert(2,printstring)
            self.table=datalist
            self.columns=len(collen)
            self.rows=rowlen
            return [datalist,len(collen),rowlen]
        else:
            raise ValueError('Invalid data provided in argument#1')

if __name__ == "__main__":
    citytable=tablemaker([["1","Tokyo","Japan","38,001,000"],
                          ["2","Delhi","India","25,703,168"],
                          ["3","Shanghai","China","23,704,778"],
                          ["4","Sao Paulo","Brazil","21,066,245"],
                          ["5","Mumbai","India","21,042,538"],
                          ["6","Mexico City","Mexico","20,998,543"],
                          ["7","Beijing","China","20,383,994"],
                          ["8","Osaka","China","20,237,645"],
                          ["9","Cairo","Egypt","18,771,769"]],
                         "Largest Cities by Population",
                         ["Rank","City","Country","Population"])
    citytable.print()
    print("\nUpdating incorrect value at row 6, column 2 i.e. China to Japan\n")
    citytable.value(6,2,"Japan")
    citytable.update()
    citytable.print()
    #print(citytable.table)
Cheers,
iMu
Reply
#2
The __all_strings() feature doesn't depend on the instance. You could provide it separately using functools.singledispatch()
from functools import singledispatch

@singledispatch
def all_string(ob):
    return str(ob).strip()

@all_string.register(tuple)
@all_string.register(list)
@all_string.register(set)
def _(ob):
    return type(ob)(all_string(x) for x in ob)

@all_string.register(dict)
def _(ob):
    return type(ob)(all_string(x) for x in ob.items())


#-------- TEST CODE --------

if __name__ == '__main__':
    import unittest as ut
    
    class TestAllString(ut.TestCase):
        def test_all_string_for_string_argument(self):
            sample = "Hello world  "
            result = all_string(sample)
            self.assertEqual(result, sample.strip())
            
        def test_all_string_for_list_argument(self):
            sample = ['a', 32, b'c', (1,2,3)]
            result = all_string(sample)
            self.assertEqual(result, ['a', '32', "b'c'", ('1', '2', '3')])
    
        def test_all_string_for_tuple_argument(self):
            sample = ('a', 32, b'c', [1,2,3])
            result = all_string(sample)
            self.assertEqual(result, ('a', '32', "b'c'", ['1', '2', '3']))

        def test_all_string_for_set_argument(self):
            sample = set(('a', 32, b'c', (1,2,3)))
            result = all_string(sample)
            self.assertEqual(result, set(('a', '32', "b'c'", ('1', '2', '3'))))

        def test_all_string_for_dict_argument(self):
            sample = {'a':32, b'c': (1,2,3)}
            result = all_string(sample)
            self.assertEqual(result, {'a': '32', "b'c'": ('1', '2', '3')})
    
    ut.main()
Reply
#3
(Apr-09-2019, 09:03 AM)Gribouillis Wrote: The __all_strings() feature doesn't depend on the instance. You could provide it separately using functools.singledispatch()
from functools import singledispatch

@singledispatch
def all_string(ob):
    return str(ob).strip()

@all_string.register(tuple)
@all_string.register(list)
@all_string.register(set)
def _(ob):
    return type(ob)(all_string(x) for x in ob)

@all_string.register(dict)
def _(ob):
    return type(ob)(all_string(x) for x in ob.items())


#-------- TEST CODE --------

if __name__ == '__main__':
    import unittest as ut
    
    class TestAllString(ut.TestCase):
        def test_all_string_for_string_argument(self):
            sample = "Hello world  "
            result = all_string(sample)
            self.assertEqual(result, sample.strip())
            
        def test_all_string_for_list_argument(self):
            sample = ['a', 32, b'c', (1,2,3)]
            result = all_string(sample)
            self.assertEqual(result, ['a', '32', "b'c'", ('1', '2', '3')])
    
        def test_all_string_for_tuple_argument(self):
            sample = ('a', 32, b'c', [1,2,3])
            result = all_string(sample)
            self.assertEqual(result, ('a', '32', "b'c'", ['1', '2', '3']))

        def test_all_string_for_set_argument(self):
            sample = set(('a', 32, b'c', (1,2,3)))
            result = all_string(sample)
            self.assertEqual(result, set(('a', '32', "b'c'", ('1', '2', '3'))))

        def test_all_string_for_dict_argument(self):
            sample = {'a':32, b'c': (1,2,3)}
            result = all_string(sample)
            self.assertEqual(result, {'a': '32', "b'c'": ('1', '2', '3')})
    
    ut.main()

Thanks and wow that functools library is some really awesome mojo. I'm gonna read up more on it.

I don't understand the testing part very well, i see you seem to be checking if all_string is working as expected, I think.
I'll check out some tutorials on how the unittest class works and ask you if i don't understand something.

Thanks again
Reply
#4
PEP8 my dudes. It does help lol. Sure helped me!
Reply
#5
(Apr-09-2019, 08:39 PM)Ceegen Wrote: PEP8 my dudes. It does help lol. Sure helped me!

Ran the original through pylint and made some changes. Some pylint warnings are still there which i don't understand how to remove.

"""Table Maker class for making ASCII formatted tables from provided data"""

class TableMaker:
    """
    anylist :   Any list, tuple, set or string to be printed in ascii enclosed
                boxes (two dimensional maximum)
    title   :   Optional title for the list to be printed on top of the data
    headers :   Optional headers to be printed (Provide as a one dimensional list
                with each list item representing a column heading)
    """

    def __init__(self, provideddata, title, providedheaders):
        self.anydata = list(map(list, self.__all_strings(provideddata[:])))
        self.__addresses = self.__generate_addresses()
        self.title = title
        if not isinstance(self.title, str):
            raise TypeError('String expected for argument#2')
        self.headers = providedheaders
        if self.headers != "":
            self.headers = self.__all_strings(providedheaders)
        self.table = []
        self.columns = 0
        self.rows = 0
        self.__process(self.__all_strings(self.anydata))

    def __generate_addresses(self):
        addressrange = []
        maincounter = 0
        innercounter = 0
        for each in self.anydata:
            if isinstance(each, (list, tuple, set)):
                for eachinner in each:
                    addressrange.append([maincounter, innercounter])
                    innercounter = innercounter+1
                maincounter = maincounter+1
                innercounter = 0
            else:
                addressrange.append([maincounter, innercounter])
                maincounter = maincounter+1
        return addressrange

    def value(self, row, col, value):
        """Insert value into table"""
        if value != "":
            if [row, col] in self.__addresses:
                try:
                    self.anydata[row][col] = value
                except IndexError:
                    self.anydata[row] = value
            else:
                columnfound = [0, False]
                for loop in range(col, -1, -1):
                    if [row, loop] in self.__addresses:
                        columnfound = [loop, True]
                        break
                if columnfound[1]:
                    if not isinstance(self.anydata[row], list):
                        self.anydata[row] = [self.anydata[row]]
                    for loop in range(columnfound[0]+1, col):
                        self.anydata[row].append("")
                    self.anydata[row].append(value)
                else:
                    rowfound = [0, False]
                    for loop in range(row, -1, -1):
                        if [loop, 0] in self.__addresses:
                            rowfound = [loop, True]
                            break
                    if rowfound[1]:
                        for loop in range(rowfound[0]+1, row+1):
                            self.anydata.append([])
                        for loop in range(1, col+1):
                            self.anydata[row].append("")
                        self.anydata[row].append(value)
            self.__addresses = self.__generate_addresses()
        else:
            raise ValueError('Invalid data provided in argument#3')

    def update(self):
        """Update the table"""
        self.__process(self.__all_strings(self.anydata))

    def print(self):
        """Print the table"""
        print("\n".join(self.table))

    def __all_strings(self, data):
        """Courtesy of Yoriz"""
        if isinstance(data, list):
            items = []
            for item in data:
                items.append(self.__all_strings(item))
            return items
        elif isinstance(data, tuple):
            return tuple(self.__all_strings(list(data)))
        elif isinstance(data, dict):
            new_dict = {}
            for key, value in data.items():
                new_dict[self.__all_strings(key)] = self.__all_strings(value)
            return new_dict
        elif isinstance(data, set):
            return set(self.__all_strings(list(data)))
        else:
            return str(data).strip()

    def __process(self, receivedlist):
        anylist = []
        anylist = receivedlist[:]
        anylist = [i if i != [] else "" for i in anylist]
        if anylist:
            if self.headers != "":
                anylist.insert(0, self.headers)
            datalist = []
            maxlen = len(self.title)
            collen = []
            maxsubentry = 1
            for entries in anylist:
                if isinstance(entries, (list, tuple)):
                    if len(entries) > maxsubentry:
                        maxsubentry = len(entries)
                    for subentry in entries:
                        if len(subentry) > maxlen:
                            maxlen = len(subentry)
                if len(entries) > maxlen:
                    maxlen = len(entries)
            collen = [0 for each in range(0, maxsubentry)]
            for entries in anylist:
                if isinstance(entries, (list, tuple)):
                    for subentry in entries:
                        if len(subentry) > collen[entries.index(subentry)]:
                            collen[entries.index(subentry)] = len(subentry)
                else:
                    if len(entries) > collen[0]:
                        collen[0] = len(entries)
            checklen = 0
            for each in collen:
                checklen += each
            if maxlen > checklen:
                collen[-1] = collen[-1]+(maxlen-checklen)
            else:
                maxlen = checklen
            if self.title != "":
                printstring = "+"+"-"*(maxlen+maxsubentry-1)+"+"
                datalist.append(printstring)
                printstring = "|"+self.title.upper()+" "*((maxlen+maxsubentry-1)-
                                                          len(self.title))+"|"
                datalist.append(printstring)
                printstring = "|"+"-"*(maxlen+maxsubentry-1)+"|"
                datalist.append(printstring)
            else:
                printstring = "+"+"-"*(maxlen+maxsubentry-1)+"+"
                datalist.append(printstring)
            for entries in anylist:
                if isinstance(entries, (list, tuple)):
                    printstring = ""
                    counter = 0
                    for subentry in entries:
                        printstring = printstring+"|"+subentry+" "*(collen[counter]-
                                                                    len(subentry))
                        counter += 1
                    if len(entries) < maxsubentry:
                        for count in range(len(entries)+1, maxsubentry+1):
                            printstring = printstring+"|"+" "*(collen[counter])
                            counter += 1
                    printstring = printstring+"|"
                    datalist.append(printstring)
                else:
                    printstring = ""
                    printstring = printstring+"|"+entries+" "*(collen[0]-len(entries))
                    for count in range(0, maxsubentry-1):
                        printstring = printstring+"|"+" "*(collen[count+1])
                    printstring = printstring+"|"
                    datalist.append(printstring)
            printstring = "+"+"-"*(maxlen+maxsubentry-1)+"+"
            datalist.append(printstring)
            if self.title != "":
                rowlen = len(datalist)-4
            else:
                rowlen = len(datalist)-2
            if self.headers != "":
                rowlen = rowlen-1
            if self.headers != "":
                printstring = "+"+"-"*(maxlen+maxsubentry-1)+"+"
                if self.title != "":
                    datalist.insert(4, printstring)
                else:
                    datalist.insert(2, printstring)
            self.table = datalist
            self.columns = len(collen)
            self.rows = rowlen
            return [datalist, len(collen), rowlen]
        else:
            raise ValueError('Invalid data provided in argument#1')

if __name__ == "__main__":
    CITYTABLE = TableMaker([["1", "Tokyo", "Japan", "38,001,000"],
                            ["2", "Delhi", "India", "25,703,168"],
                            ["3", "Shanghai", "China", "23,704,778"],
                            ["4", "Sao Paulo", "Brazil", "21,066,245"],
                            ["5", "Mumbai", "India", "21,042,538"],
                            ["6", "Mexico City", "Mexico", "20,998,543"],
                            ["7", "Beijing", "China", "20,383,994"],
                            ["8", "Osaka", "China", "20,237,645"],
                            ["9", "Cairo", "Egypt", "18,771,769"]],
                           "Largest Cities by Population",
                           ["Rank", "City", "Country", "Population"])
    CITYTABLE.print()
    print("\nUpdating incorrect value at row 6, column 2 i.e. China to Japan\n")
    CITYTABLE.value(6, 2, "Japan")
    CITYTABLE.update()
    CITYTABLE.print()
Reply
#6
(Apr-10-2019, 02:34 PM)iMuny Wrote: Ran the original through pylint and made some changes. Some pylint warnings are still there which i don't understand how to remove.

Using a <YourScript.py> from py 2.7 in a 3.x interpreter, or vice-versa, may be the cause.

I've run into a lot of examples on web pages linked to from the official Python docs (here on this web page), that sometimes have outdated examples. For example, some of tkinter's option syntax has changed, and using older examples will throw errors. Know it is a lot of work to update all of it, but just from working with TkInter alone I've ran into several errors while using Python 3.6 and 3.7 when using said linked examples from the tk docs specifically. Using help(tk.Frame) [or whatever] yields some information, but in some rare cases has nothing to give you an idea of what is really going on.

Am no authority on anything, just started also, but it does help to warn others: It's a struggle all the way lol. Document everything you do, and remember to keep notes. Whatever it takes to get you to write more, in addition to writing more code. It helped me to have a routine, and make it a point to learn some new function or feature, or something about the inner workings of CPython or PyPy. Like, the only reason I mentioned PEP 8 was because I finally read it a few weeks ago! I had only previously learned most of it by wrote, coding it by hand, uphill in the snow both ways using only IDLE on a RaspPi3B+. (That actually isn't a joke, I still have my raspi <3).

Stay at it!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Extremely Simple RPG Character File Maker ashtons 0 2,355 Jan-09-2018, 10:46 PM
Last Post: ashtons

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020