Stephen Ellwood
Stephen Ellwood

Reputation: 424

how to create a class built on file.io

I am writing a module that converts between several different file formats (e.g. vhdl to verilog, excel table to vhdl etc). Its not so hard but there is a lot of language specific formatting to do. It just occurred to me that an elegant way to do this was to have a class type for each file format type by having a class built on file.io. The class would inherit methods of file but also the ability to read or write specific syntax to that file. I could not find any examples of a file io superclass and how to write it. My idea was that to instantiate it (open the file) i could use:

my_lib_file = Libfile(filename, 'w')

and to write a simple parameter to the libfile I could use something like

my_lib_file.simple_parameter(param, value)

Such a class would tie together the many file specific functions I currently have in a neat way. Actually I would prefer to be able to instantiate the class as part of a with statement e.g.:

with Libfile(filename, 'w') as my_lib_file:
    for param, value in my_stuff.items():
        my_lib_file.simple_parameter(param, value)

Upvotes: 2

Views: 1446

Answers (3)

Stephen Ellwood
Stephen Ellwood

Reputation: 424

I have now found a satisfactory way of doing what I wanted. The following is my base class, which is built on the base functions of file_io (but is not a subclass) and a simple example for writing CSV files. I also have Formatters for HTML, Verilog and others. Code is:

class Formatter():
    ''' base class to manage the opening of a file in order to build classes which write a file
        with a specific format so as to be able to pass the formatting functions to a subroutine
        along with the file handle

        Designed to use with "with" statement and to shorten argument lists of functions which use
        the file
    '''

    def __init__(self, filename):
        ''' associate this instance with the given file handle
        '''
        self.f = open(filename, 'w')

    def wr(self, line, end='\n'):
        ''' write line given to file
        '''
        self.f.write(line + end)


    def wr_newline(self, num):
        ''' write num newlines to file
        '''
        self.f.write('\n'*num)


    def __enter__(self):
        ''' needed for use with with statement
        '''
        return self


    def __exit__(self, *args):
        ''' needed for use with with statement
        '''
        self.close()


    def close(self):
        ''' explicit file close for use with procedural progamming style
        '''
        self.f.close()



class CSV(Formatter):
    ''' class to write items using comma separated file format string formatting
        inherrits:
        => wr(line, end='\n'):
        => wr_newline(n):
        all file io functions via the self.f variable
    '''

    @staticmethod
    def pp(item):
        ''' 'pretty-print - can't have , or \n in strings used in CSV files
        '''
        return  str(item).replace('\n', '/').replace(',', '-')


    def __init__(self, filename):
        '''open filen given as a CSV file
        '''
        super().__init__(filename + '.csv')


    def wp(self, item):
        ''' write a single item to the file
        '''
        self.f.write(self.pp(item)+', ')


    def ws(self, itemlist):
        ''' write a csv list from a list variable
        '''
        self.wr(','.join([self.pp(item) for item in itemlist]))

Upvotes: 0

Open AI - Opting Out
Open AI - Opting Out

Reputation: 24133

This is the wrong way to think about it.

You inherit in order to be reused. The base class provides an interface which others can use. For file-like objects it's mainly read and write. But, you only want to call another function simple_parameter. Calling write directly could mess up the format.

Really you don't want it to be a file-like object. You want to write to a file when the user calls simple_parameter. The implementation should delegate to a member file-like object instead, e.g.:

class LibFile:
    def __init__(self, file):
        self.file = file

    def simple_parameter(self, param, value):
        self.file.write('{}: {}\n'.format(param, value))

This is easy to test as you could pass in anything that supports write:

>>> import sys
>>> lib = LibFile(sys.stdout)
>>> lib.simple_parameter('name', 'Stephen')
name: Stephen

edit:

If you really want the class to manage the lifetime of the file you can provide a close function and use the closing context manager:

class Formatter:
    def __init__(self, filename, mode):
        self.file = open(filename, mode)

    def close(self):
        self.file.close()

Usage:

class LibFormatter(Formatter):
    def simple_parameter(self, param, value):
        self.file.write('{}: {}\n'.format(param, value))

from contextlib import closing

with closing(LibFormatter('library.txt', 'w')) as lib:
    ... # etc

2nd edit:

If you don't want to use closing, you can write your own context manager:

class ManagedFile:
    def __init__(self, filename, mode):
        self.file = open(filename, mode)

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.close()

    def close(self):
        self.file.close()

Usage:

class LibFormatter(ManagedFile):
    def simple_parameter(self, param, value):
        self.file.write('{}: {}\n'.format(param, value))

with LibFormatter('library.txt', 'w') as lib:
    ... # etc

Upvotes: 4

Stephen Ellwood
Stephen Ellwood

Reputation: 424

My two line solution is as follows:

with open(lib_loc + '\\' + lib_name + '.lib', 'w') as lib_file_handle:
    lib_file = Liberty(lib_file_handle)
    # do stuff using lib_file

the class initialization is as follows:

def __init__(self, file):
    ''' associate this instance with the given file handle '''
    self.f = file

now instead of passing the raw file handle I pass the class along with the functions to my functions.

The simplest function is:

def wr(self, line):
    ''' write line given to file'''
    self.f.write(line + '\n')

Which means I am replicating the write function built into the file.io class. This was what I was trying to avoid.

Upvotes: 0

Related Questions